ZAP Scanning Report
| Risk Level | Number of Alerts |
|---|---|
|
High
|
1
|
|
Medium
|
3
|
|
Low
|
4
|
|
Informational
|
8
|
|
False Positives:
|
0
|
| Name | Risk Level | Number of Instances |
|---|---|---|
| Cross Site Scripting (Reflected) | High | 1 |
| Absence of Anti-CSRF Tokens | Medium | 8 |
| Content Security Policy (CSP) Header Not Set | Medium | 75 |
| Missing Anti-clickjacking Header | Medium | 73 |
| Cookie No HttpOnly Flag | Low | 3 |
| Cookie without SameSite Attribute | Low | 3 |
| Strict-Transport-Security Header Not Set | Low | 28 |
| X-Content-Type-Options Header Missing | Low | 93 |
| Charset Mismatch (Header Versus Meta Content-Type Charset) | Informational | 12 |
| Cookie Poisoning | Informational | 4 |
| Information Disclosure - Suspicious Comments | Informational | 2 |
| Modern Web Application | Informational | 19 |
| Re-examine Cache-control Directives | Informational | 17 |
| Retrieved from Cache | Informational | 3 |
| Session Management Response Identified | Informational | 19 |
| User Agent Fuzzer | Informational | 48 |
| HTTP Response Code | Number of Responses |
|---|---|
| 404 Not Found |
4
|
| 200 OK |
24
|
| 302 Found |
1
|
| Authentication Statistics | Number of Responses |
|---|---|
|
!reports.report.stats.auth.sessiontoken.GRUYERE_ID!
|
2
|
| Parameter Name | Type | Flags | Times Used | # Values |
|---|
| HTTP Response Code | Number of Responses |
|---|---|
| 404 Not Found |
79
|
| 200 OK |
1767
|
| 302 Found |
51
|
| Authentication Statistics | Number of Responses |
|---|---|
|
!reports.report.stats.auth.sessiontoken.GRUYERE_ID!
|
96
|
|
!reports.report.stats.auth.sessiontoken.GRUYERE!
|
2
|
| Parameter Name | Type | Flags | Times Used | # Values |
|---|
|
High |
Cross Site Scripting (Reflected) |
|---|---|
| Description |
Cross-site Scripting (XSS) is an attack technique that involves echoing attacker-supplied code into a user's browser instance. A browser instance can be a standard web browser client, or a browser object embedded in a software product such as the browser within WinAmp, an RSS reader, or an email client. The code itself is usually written in HTML/JavaScript, but may also extend to VBScript, ActiveX, Java, Flash, or any other browser-supported technology.
When an attacker gets a user's browser to execute his/her code, the code will run within the security context (or zone) of the hosting web site. With this level of privilege, the code has the ability to read, modify and transmit any sensitive data accessible by the browser. A Cross-site Scripted user could have his/her account hijacked (cookie theft), their browser redirected to another location, or possibly shown fraudulent content delivered by the web site they are visiting. Cross-site Scripting attacks essentially compromise the trust relationship between a user and the web site. Applications utilizing browser object instances which load content from the file system may execute code under the local machine zone allowing for system compromise.
There are three types of Cross-site Scripting attacks: non-persistent, persistent and DOM-based.
Non-persistent attacks and DOM-based attacks require a user to either visit a specially crafted link laced with malicious code, or visit a malicious web page containing a web form, which when posted to the vulnerable site, will mount the attack. Using a malicious form will oftentimes take place when the vulnerable resource only accepts HTTP POST requests. In such a case, the form can be submitted automatically, without the victim's knowledge (e.g. by using JavaScript). Upon clicking on the malicious link or submitting the malicious form, the XSS payload will get echoed back and will get interpreted by the user's browser and execute. Another technique to send almost arbitrary requests (GET and POST) is by using an embedded client, such as Adobe Flash.
Persistent attacks occur when the malicious code is submitted to a web site where it's stored for a period of time. Examples of an attacker's favorite targets often include message board posts, web mail messages, and web chat software. The unsuspecting user is not required to interact with any additional site/link (e.g. an attacker site or a malicious link sent via email), just simply view the web page containing the code.
|
| URL | http://google-gruyere.appspot.com/397587545265662818066921574239585949762/snippets.gtl?uid=%3C%2Fh2%3E%3CscrIpt%3Ealert%281%29%3B%3C%2FscRipt%3E%3Ch2%3E |
| Method | GET |
| Parameter | uid |
| Attack | </h2><scrIpt>alert(1);</scRipt><h2> |
| Evidence | </h2><scrIpt>alert(1);</scRipt><h2> |
| Request Header - size: 456 bytes. |
GET http://google-gruyere.appspot.com/397587545265662818066921574239585949762/snippets.gtl?uid=%3C%2Fh2%3E%3CscrIpt%3Ealert%281%29%3B%3C%2FscRipt%3E%3Ch2%3E HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/397587545265662818066921574239585949762/ |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 249 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: 43921345482dc2ca6d731223737af55c Date: Tue, 06 Feb 2024 17:43:08 GMT Server: Google Frontend Content-Length: 2655 |
| Response Body - size: 2,655 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Snippets</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> <script src="/397587545265662818066921574239585949762/lib.js" text="text/javascript"> </script> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/397587545265662818066921574239585949762/'>Home</a> </span> <span id='menu-right'> <a href='/397587545265662818066921574239585949762/login'>Sign in</a> | <a href='/397587545265662818066921574239585949762/newaccount.gtl'>Sign up</a> </span> </div> <div> <h2 class='has-refresh' id="user_name"> </h2><scrIpt>alert(1);</scRipt><h2> </h2> <div class='refresh'><a class='button' onclick='_refreshSnippets("397587545265662818066921574239585949762", "</h2><scrIpt>alert(1);</scRipt><h2>")' href='#'>Refresh</a></div> <div class='content'> </h2><scrIpt>alert(1);</scRipt><h2> is not an author. </div> </body> </html> |
| Instances | 1 |
| Solution |
Phase: Architecture and Design
Use a vetted library or framework that does not allow this weakness to occur or provides constructs that make this weakness easier to avoid.
Examples of libraries and frameworks that make it easier to generate properly encoded output include Microsoft's Anti-XSS library, the OWASP ESAPI Encoding module, and Apache Wicket.
Phases: Implementation; Architecture and Design
Understand the context in which your data will be used and the encoding that will be expected. This is especially important when transmitting data between different components, or when generating outputs that can contain multiple encodings at the same time, such as web pages or multi-part mail messages. Study all expected communication protocols and data representations to determine the required encoding strategies.
For any data that will be output to another web page, especially any data that was received from external inputs, use the appropriate encoding on all non-alphanumeric characters.
Consult the XSS Prevention Cheat Sheet for more details on the types of encoding and escaping that are needed.
Phase: Architecture and Design
For any security checks that are performed on the client side, ensure that these checks are duplicated on the server side, in order to avoid CWE-602. Attackers can bypass the client-side checks by modifying values after the checks have been performed, or by changing the client to remove the client-side checks entirely. Then, these modified values would be submitted to the server.
If available, use structured mechanisms that automatically enforce the separation between data and code. These mechanisms may be able to provide the relevant quoting, encoding, and validation automatically, instead of relying on the developer to provide this capability at every point where output is generated.
Phase: Implementation
For every web page that is generated, use and specify a character encoding such as ISO-8859-1 or UTF-8. When an encoding is not specified, the web browser may choose a different encoding by guessing which encoding is actually being used by the web page. This can cause the web browser to treat certain sequences as special, opening up the client to subtle XSS attacks. See CWE-116 for more mitigations related to encoding/escaping.
To help mitigate XSS attacks against the user's session cookie, set the session cookie to be HttpOnly. In browsers that support the HttpOnly feature (such as more recent versions of Internet Explorer and Firefox), this attribute can prevent the user's session cookie from being accessible to malicious client-side scripts that use document.cookie. This is not a complete solution, since HttpOnly is not supported by all browsers. More importantly, XMLHTTPRequest and other powerful browser technologies provide read access to HTTP headers, including the Set-Cookie header in which the HttpOnly flag is set.
Assume all input is malicious. Use an "accept known good" input validation strategy, i.e., use an allow list of acceptable inputs that strictly conform to specifications. Reject any input that does not strictly conform to specifications, or transform it into something that does. Do not rely exclusively on looking for malicious or malformed inputs (i.e., do not rely on a deny list). However, deny lists can be useful for detecting potential attacks or determining which inputs are so malformed that they should be rejected outright.
When performing input validation, consider all potentially relevant properties, including length, type of input, the full range of acceptable values, missing or extra inputs, syntax, consistency across related fields, and conformance to business rules. As an example of business rule logic, "boat" may be syntactically valid because it only contains alphanumeric characters, but it is not valid if you are expecting colors such as "red" or "blue."
Ensure that you perform input validation at well-defined interfaces within the application. This will help protect the application even if a component is reused or moved elsewhere.
|
| Reference |
https://owasp.org/www-community/attacks/xss/
https://cwe.mitre.org/data/definitions/79.html |
| Tags |
OWASP_2021_A03
WSTG-v42-INPV-01 OWASP_2017_A07 |
| CWE Id | 79 |
| WASC Id | 8 |
| Plugin Id | 40012 |
|
Medium |
Absence of Anti-CSRF Tokens |
|---|---|
| Description |
No Anti-CSRF tokens were found in a HTML submission form.
A cross-site request forgery is an attack that involves forcing a victim to send an HTTP request to a target destination without their knowledge or intent in order to perform an action as the victim. The underlying cause is application functionality using predictable URL/form actions in a repeatable way. The nature of the attack is that CSRF exploits the trust that a web site has for a user. By contrast, cross-site scripting (XSS) exploits the trust that a user has for a web site. Like XSS, CSRF attacks are not necessarily cross-site, but they can be. Cross-site request forgery is also known as CSRF, XSRF, one-click attack, session riding, confused deputy, and sea surf.
CSRF attacks are effective in a number of situations, including:
* The victim has an active session on the target site.
* The victim is authenticated via HTTP auth on the target site.
* The victim is on the same local network as the target site.
CSRF has primarily been used to perform an action against a target site using the victim's privileges, but recent techniques have been discovered to disclose information by gaining access to the response. The risk of information disclosure is dramatically increased when the target site is vulnerable to XSS, because XSS can be used as a platform for CSRF, allowing the attack to operate within the bounds of the same-origin policy.
|
| URL | http://google-gruyere.appspot.com/382665580745386307547168512335551204731/login |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | <form method='get' action='/382665580745386307547168512335551204731/login'> |
| Request Header - size: 443 bytes. |
GET http://google-gruyere.appspot.com/382665580745386307547168512335551204731/login HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/382665580745386307547168512335551204731/ Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 249 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: 16b439f06534331a20b8802d36a3b6fa Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 2591 |
| Response Body - size: 2,591 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Login</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/382665580745386307547168512335551204731/'>Home</a> </span> <span id='menu-right'> <a href='/382665580745386307547168512335551204731/login'>Sign in</a> | <a href='/382665580745386307547168512335551204731/newaccount.gtl'>Sign up</a> </span> </div> <div> <h2>Gruyere: Login</h2> </div> <div class='content'> <form method='get' action='/382665580745386307547168512335551204731/login'> <table><tr><td> User name: </td><td> <input type='text' name='uid'> </td></tr><tr><td> Password: </td><td> <input type='password' name='pw'> </td></tr><tr><td></td><td align='right'> <input type='submit' value='Login'> </td></tr></table> </form> </div> </body> </html> |
| URL | http://google-gruyere.appspot.com/382665580745386307547168512335551204731/login?pw=ZAP&uid=ZAP |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | <form method='get' action='/382665580745386307547168512335551204731/login'> |
| Request Header - size: 463 bytes. |
GET http://google-gruyere.appspot.com/382665580745386307547168512335551204731/login?pw=ZAP&uid=ZAP HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/382665580745386307547168512335551204731/login Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 249 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: 1b0b3f007d98bdce40097f85480aebda Date: Tue, 06 Feb 2024 17:42:25 GMT Server: Google Frontend Content-Length: 2650 |
| Response Body - size: 2,650 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Login</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/382665580745386307547168512335551204731/'>Home</a> </span> <span id='menu-right'> <a href='/382665580745386307547168512335551204731/login'>Sign in</a> | <a href='/382665580745386307547168512335551204731/newaccount.gtl'>Sign up</a> </span> </div> <div> <h2>Gruyere: Login</h2> </div> <div class='message'>Invalid user name or password.</div> <div class='content'> <form method='get' action='/382665580745386307547168512335551204731/login'> <table><tr><td> User name: </td><td> <input type='text' name='uid'> </td></tr><tr><td> Password: </td><td> <input type='password' name='pw'> </td></tr><tr><td></td><td align='right'> <input type='submit' value='Login'> </td></tr></table> </form> </div> </body> </html> |
| URL | http://google-gruyere.appspot.com/382665580745386307547168512335551204731/newaccount.gtl |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | <form method='get' action='/382665580745386307547168512335551204731/saveprofile'> |
| Request Header - size: 452 bytes. |
GET http://google-gruyere.appspot.com/382665580745386307547168512335551204731/newaccount.gtl HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/382665580745386307547168512335551204731/ Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 249 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: 81fd781cfd9ee13ea54b5718c50baeed Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 2961 |
| Response Body - size: 2,961 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Profile</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/382665580745386307547168512335551204731/'>Home</a> </span> <span id='menu-right'> <a href='/382665580745386307547168512335551204731/login'>Sign in</a> | <a href='/382665580745386307547168512335551204731/newaccount.gtl'>Sign up</a> </span> </div> <h2>Gruyere: Sign up</h2> <div class='content'> <h3>Sign up for a new account.</h3> <form method='get' action='/382665580745386307547168512335551204731/saveprofile'> <input type='hidden' name='action' value='new'> <table><tr><td> User name: </td><td> <input type='text' name='uid' value='' maxlength='16'> </td></tr> <tr><td> Password: </td><td> <input type='password' name='pw'> <br><span style="color:red"><b>WARNING: Gruyere is not secure.<br> Do not use a password that you use for any real service.<br> Do not upload any personal or private data.</b></span> </td></tr> <input type='hidden' name='is_author' value='True'> <tr><td></td><td align='right'> <input type='submit' value='Create account'> </td></tr> </table> </form> </div> </body> </html> |
| URL | http://google-gruyere.appspot.com/397587545265662818066921574239585949762/login |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | <form method='get' action='/397587545265662818066921574239585949762/login'> |
| Request Header - size: 383 bytes. |
GET http://google-gruyere.appspot.com/397587545265662818066921574239585949762/login HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/397587545265662818066921574239585949762/ |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 249 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: 9083b154ecc26c292a9c737c8fa7a655 Date: Tue, 06 Feb 2024 17:42:17 GMT Server: Google Frontend Content-Length: 2591 |
| Response Body - size: 2,591 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Login</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/397587545265662818066921574239585949762/'>Home</a> </span> <span id='menu-right'> <a href='/397587545265662818066921574239585949762/login'>Sign in</a> | <a href='/397587545265662818066921574239585949762/newaccount.gtl'>Sign up</a> </span> </div> <div> <h2>Gruyere: Login</h2> </div> <div class='content'> <form method='get' action='/397587545265662818066921574239585949762/login'> <table><tr><td> User name: </td><td> <input type='text' name='uid'> </td></tr><tr><td> Password: </td><td> <input type='password' name='pw'> </td></tr><tr><td></td><td align='right'> <input type='submit' value='Login'> </td></tr></table> </form> </div> </body> </html> |
| URL | http://google-gruyere.appspot.com/397587545265662818066921574239585949762/login?pw=ZAP&uid=ZAP |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | <form method='get' action='/397587545265662818066921574239585949762/login'> |
| Request Header - size: 463 bytes. |
GET http://google-gruyere.appspot.com/397587545265662818066921574239585949762/login?pw=ZAP&uid=ZAP HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/397587545265662818066921574239585949762/login Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 249 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: 9899d637fd9ca7dddfd483f9210096d5 Date: Tue, 06 Feb 2024 17:42:18 GMT Server: Google Frontend Content-Length: 2650 |
| Response Body - size: 2,650 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Login</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/397587545265662818066921574239585949762/'>Home</a> </span> <span id='menu-right'> <a href='/397587545265662818066921574239585949762/login'>Sign in</a> | <a href='/397587545265662818066921574239585949762/newaccount.gtl'>Sign up</a> </span> </div> <div> <h2>Gruyere: Login</h2> </div> <div class='message'>Invalid user name or password.</div> <div class='content'> <form method='get' action='/397587545265662818066921574239585949762/login'> <table><tr><td> User name: </td><td> <input type='text' name='uid'> </td></tr><tr><td> Password: </td><td> <input type='password' name='pw'> </td></tr><tr><td></td><td align='right'> <input type='submit' value='Login'> </td></tr></table> </form> </div> </body> </html> |
| URL | http://google-gruyere.appspot.com/397587545265662818066921574239585949762/newaccount.gtl |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | <form method='get' action='/397587545265662818066921574239585949762/saveprofile'> |
| Request Header - size: 392 bytes. |
GET http://google-gruyere.appspot.com/397587545265662818066921574239585949762/newaccount.gtl HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/397587545265662818066921574239585949762/ |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 249 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: a9898974a14997c3d97dfe9c098c8645 Date: Tue, 06 Feb 2024 17:42:18 GMT Server: Google Frontend Content-Length: 2961 |
| Response Body - size: 2,961 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Profile</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/397587545265662818066921574239585949762/'>Home</a> </span> <span id='menu-right'> <a href='/397587545265662818066921574239585949762/login'>Sign in</a> | <a href='/397587545265662818066921574239585949762/newaccount.gtl'>Sign up</a> </span> </div> <h2>Gruyere: Sign up</h2> <div class='content'> <h3>Sign up for a new account.</h3> <form method='get' action='/397587545265662818066921574239585949762/saveprofile'> <input type='hidden' name='action' value='new'> <table><tr><td> User name: </td><td> <input type='text' name='uid' value='' maxlength='16'> </td></tr> <tr><td> Password: </td><td> <input type='password' name='pw'> <br><span style="color:red"><b>WARNING: Gruyere is not secure.<br> Do not use a password that you use for any real service.<br> Do not upload any personal or private data.</b></span> </td></tr> <input type='hidden' name='is_author' value='True'> <tr><td></td><td align='right'> <input type='submit' value='Create account'> </td></tr> </table> </form> </div> </body> </html> |
| URL | https://google-gruyere.appspot.com/382665580745386307547168512335551204731/login |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | <form method='get' action='/382665580745386307547168512335551204731/login'> |
| Request Header - size: 456 bytes. |
GET https://google-gruyere.appspot.com/382665580745386307547168512335551204731/login HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: https://google-gruyere.appspot.com/382665580745386307547168512335551204731/quitserver. Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 306 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: 8b42cbce881c35ee8d87883b85cd8f7f Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 2591 Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 |
| Response Body - size: 2,591 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Login</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/382665580745386307547168512335551204731/'>Home</a> </span> <span id='menu-right'> <a href='/382665580745386307547168512335551204731/login'>Sign in</a> | <a href='/382665580745386307547168512335551204731/newaccount.gtl'>Sign up</a> </span> </div> <div> <h2>Gruyere: Login</h2> </div> <div class='content'> <form method='get' action='/382665580745386307547168512335551204731/login'> <table><tr><td> User name: </td><td> <input type='text' name='uid'> </td></tr><tr><td> Password: </td><td> <input type='password' name='pw'> </td></tr><tr><td></td><td align='right'> <input type='submit' value='Login'> </td></tr></table> </form> </div> </body> </html> |
| URL | https://google-gruyere.appspot.com/382665580745386307547168512335551204731/newaccount.gtl |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | <form method='get' action='/382665580745386307547168512335551204731/saveprofile'> |
| Request Header - size: 465 bytes. |
GET https://google-gruyere.appspot.com/382665580745386307547168512335551204731/newaccount.gtl HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: https://google-gruyere.appspot.com/382665580745386307547168512335551204731/quitserver. Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 306 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: e1f0d836f241aeea4e12446db611b471 Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 2961 Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 |
| Response Body - size: 2,961 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Profile</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/382665580745386307547168512335551204731/'>Home</a> </span> <span id='menu-right'> <a href='/382665580745386307547168512335551204731/login'>Sign in</a> | <a href='/382665580745386307547168512335551204731/newaccount.gtl'>Sign up</a> </span> </div> <h2>Gruyere: Sign up</h2> <div class='content'> <h3>Sign up for a new account.</h3> <form method='get' action='/382665580745386307547168512335551204731/saveprofile'> <input type='hidden' name='action' value='new'> <table><tr><td> User name: </td><td> <input type='text' name='uid' value='' maxlength='16'> </td></tr> <tr><td> Password: </td><td> <input type='password' name='pw'> <br><span style="color:red"><b>WARNING: Gruyere is not secure.<br> Do not use a password that you use for any real service.<br> Do not upload any personal or private data.</b></span> </td></tr> <input type='hidden' name='is_author' value='True'> <tr><td></td><td align='right'> <input type='submit' value='Create account'> </td></tr> </table> </form> </div> </body> </html> |
| Instances | 8 |
| Solution |
Phase: Architecture and Design
Use a vetted library or framework that does not allow this weakness to occur or provides constructs that make this weakness easier to avoid.
For example, use anti-CSRF packages such as the OWASP CSRFGuard.
Phase: Implementation
Ensure that your application is free of cross-site scripting issues, because most CSRF defenses can be bypassed using attacker-controlled script.
Phase: Architecture and Design
Generate a unique nonce for each form, place the nonce into the form, and verify the nonce upon receipt of the form. Be sure that the nonce is not predictable (CWE-330).
Note that this can be bypassed using XSS.
Identify especially dangerous operations. When the user performs a dangerous operation, send a separate confirmation request to ensure that the user intended to perform that operation.
Note that this can be bypassed using XSS.
Use the ESAPI Session Management control.
This control includes a component for CSRF.
Do not use the GET method for any request that triggers a state change.
Phase: Implementation
Check the HTTP Referer header to see if the request originated from an expected page. This could break legitimate functionality, because users or proxies may have disabled sending the Referer for privacy reasons.
|
| Reference |
https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html
https://cwe.mitre.org/data/definitions/352.html |
| Tags |
OWASP_2021_A01
WSTG-v42-SESS-05 OWASP_2017_A05 |
| CWE Id | 352 |
| WASC Id | 9 |
| Plugin Id | 10202 |
|
Medium |
Content Security Policy (CSP) Header Not Set |
|---|---|
| Description |
Content Security Policy (CSP) is an added layer of security that helps to detect and mitigate certain types of attacks, including Cross Site Scripting (XSS) and data injection attacks. These attacks are used for everything from data theft to site defacement or distribution of malware. CSP provides a set of standard HTTP headers that allow website owners to declare approved sources of content that browsers should be allowed to load on that page — covered types are JavaScript, CSS, HTML frames, fonts, images and embeddable objects such as Java applets, ActiveX, audio and video files.
|
| URL | http://google-gruyere.appspot.com/ |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 359 bytes. |
GET http://google-gruyere.appspot.com/ HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/2 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 244 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: d7fb23bba6f0427f282d46dfaf994463 Date: Tue, 06 Feb 2024 17:42:18 GMT Server: Google Frontend Content-Length: 11506 |
| Response Body - size: 11,506 bytes. |
<HTML><HEAD><META http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<TITLE>Web Application Exploits and Defenses</TITLE> <LINK type="text/css" rel="stylesheet" href="../static/codelab.css"> <!--MARK-Z--> <SCRIPT> function toggleBlock(heading, whichID) { var image = heading.childNodes[0]; var block = document.getElementById(whichID); if (block) { if (getDisplay(block) == 'block') { block.style.display = 'none'; image.src = 'static/closed.gif'; } else { // "none" or "" block.style.display = 'block'; image.src = 'static/open.gif'; } } } function getDisplay(block) { var value = block.style.display; if (!value) { if (document.defaultView) { var computedStyle = document.defaultView.getComputedStyle(block, ""); value = computedStyle.getPropertyValue('display'); } else if (block.currentStyle) { value = block.currentStyle.display; } } return value; } </SCRIPT> </HEAD> <BODY bgcolor="#ffffff"> <DIV class="banner"></DIV> <H1><FONT size="+2"><IMG src="/static/gruyere-78.png" style="vertical-align:middle"> Web Application Exploits and Defenses <!--PART#--></FONT> </H1> <P> A Codelab by Bruce Leban, Mugdha Bendre, and Parisa Tabriz </P> <DIV class="printable"> <DIV class="column1"> Table of Contents <UL> <LI class="L1" style="padding-top:0pt"> <A href="/#0__hackers">Beat the hackers</A></LI> <LI class="L1" style="padding-top:0pt"> <A href="/#0__gruyere">Gruyere</A></LI> <LI class="L1"> <A href="/part1#1__setup">Set-up</A> <UL> <LI class="L2"> <A href="/part1#1__reset_button">Reset Button</A></LI> <LI class="L2"> <A href="/part1#1__about_the_code">About the Code</A></LI> <LI class="L2"> <A href="/part1#1__features_and_technologies">Features and Technologies</A></LI> </UL></LI> <LI class="L1"> <A href="/part1#1__using_gruyere">Using Gruyere</A></LI> <LI class="L1"> <A href="/part2#2__cross_site_scripting">Cross-Site Scripting (XSS)</A> <UL> <LI class="L2"> <A href="/part2#2__xss_challenge">XSS Challenges</A></LI> <LI class="L2"> <A href="/part2#2__file_upload_xss">File Upload XSS</A></LI> <LI class="L2"> <A href="/part2#2__reflected_xss">Reflected XSS</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss">Stored XSS</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss_via_html_attribute">Stored XSS via HTML Attribute</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss_via_ajax">Stored XSS via AJAX</A></LI> <LI class="L2"> <A href="/part2#2__reflected_xss_via_ajax">Reflected XSS via AJAX</A></LI> <LI class="L2"> <A href="/part2#2__more_about_xss">More about XSS</A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__client_state_manipulation">Client-State Manipulation</A> <UL> <LI class="L2"> <A href="/part3#3__elevation_of_privilege"> Elevation of Privilege </A></LI> <LI class="L2"> <A href="/part3#3__cookie_manipulation"> Cookie Manipulation </A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__cross_site_request_forgery">Cross-Site Request Forgery (XSRF)</A> <UL> <LI class="L2"> <A href="/part3#3__xsrf_challenge">XSRF Challenge</A></LI> <LI class="L2"> <A href="/part3#3__more_about_preventing_xsrf">More about preventing XSRF</A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__cross_site_script_inclusion">Cross Site Script Inclusion (XSSI)</A> <UL> <LI class="L2"> <A href="/part3#3__xssi_challenge">XSSI Challenge</A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__path_traversal">Path Traversal</A> <UL> <LI class="L2"> <A href="/part4#4__information_disclosure_path_traversal">Information disclosure via path traversal </A></LI> <LI class="L2"> <A href="/part4#4__data_tampering_path_traversal">Data tampering via path traversal </A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__denial_of_service">Denial of Service</A> <UL> <LI class="L2"> <A href="/part4#4__dos_quit_server">DoS - Quit the Server</A></LI> <LI class="L2"> <A href="/part4#4__dos_overload_server">DoS - Overloading the Server</A></LI> <LI class="L2"> <A href="/part4#4__more_dos">More on Denial of Service</A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__code_execution">Code Execution</A> <UL> <LI class="L2"> <A href="/part4#4__code_execution_challenge">Code Execution Challenge</A></LI> <LI class="L2"> <A href="/part4#4__more_code_execution">More on Remote Code Execution</A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__configuration_vulnerabilities">Configuration Vulnerabilities</A> <UL> <LI class="L2"> <A href="/part5#5__information_disclosure_config_1">Information disclosure #1</A></LI> <LI class="L2"> <A href="/part5#5__information_disclosure_config_2">Information disclosure #2</A></LI> <LI class="L2"> <A href="/part5#5__information_disclosure_bug_3">Information disclosure #3 </A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__ajax_vulnerabilities">AJAX vulnerabilities</A> <UL> <LI class="L2"> <A href="/part5#5__dos_via_ajax">DoS via AJAX</A></LI> <LI class="L2"> <A href="/part5#5__phishing_via_ajax">Phishing via AJAX</A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__other_vulnerabilities"> Other Vulnerabilities </A> </H2> <UL> <LI class="L2"> <A href="/part5#5__buffer_and_integer_overflow">Buffer Overflow and Integer Overflow</A></LI> <LI class="L2"> <A href="/part5#5__sql_injection"> SQL Injection </A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__after_the_codelab">After the Codelab</A></LI> </UL> </DIV> <DIV class="column2"> <P></P> <P></P> <P> <NOAUTOLINK> </NOAUTOLINK></P> <STYLE>.column1 {display:none}</STYLE> <BR><P> <H2><A name="0__hackers"></A>Want to beat the hackers at their own game?</H2> <UL> <LI>Learn how hackers find security vulnerabilities! <LI>Learn how hackers exploit web applications! <LI>Learn how to stop them! </UL> </P> <!--MARK-0--> <P> This codelab shows how web application vulnerabilities can be exploited and how to defend against these attacks. The best way to learn things is by doing, so you'll get a chance to do some real penetration testing, actually exploiting a real application. Specifically, you'll learn the following: </P> <P></P> <UL> <LI> How an application can be attacked using common web security vulnerabilities, like cross-site scripting vulnerabilities (XSS) and cross-site request forgery (XSRF). </LI> <LI> How to find, fix, and avoid these common vulnerabilities and other bugs that have a security impact, such as denial-of-service, information disclosure, or remote code execution. </LI> </UL> <P> To get the most out of this lab, you should have some familiarity with how a web application works (e.g., general knowledge of HTML, templates, cookies, AJAX, etc.). </P> <!--MARK-1--> <BR> <BR> <H2><A name="1__gruyere"> </A> Gruyere </H2> <P> <A href="/static/gruyere.png"> <IMG src="/static/gruyere.png" height="285" border="0" style="float:left; vertical-align:middle; margin-right: 10; margin-bottom: 10"> </A> This codelab is built around <B>Gruyere</B> /ɡruːˈjɛər/ <!--groo-yair--> - a small, cheesy web application that allows its users to publish snippets of text and store assorted files. "Unfortunately," Gruyere has multiple security bugs ranging from cross-site scripting and cross-site request forgery, to information disclosure, denial of service, and remote code execution. The goal of this codelab is to guide you through discovering some of these bugs and learning ways to fix them both in Gruyere and in general. </P> <P> The codelab is organized by types of vulnerabilities. In each section, you'll find a brief description of a vulnerability and a task to find an instance of that vulnerability in Gruyere. Your job is to play the role of a malicious hacker and find and exploit the security bugs. In this codelab, you'll use both black-box hacking and white-box hacking. In <B>black box hacking,</B> you try to find security bugs by experimenting with the application and manipulating input fields and URL parameters, trying to cause application errors, and looking at the HTTP requests and responses to guess server behavior. You do not have access to the source code, although understanding how to view source and being able to view http headers (as you can in Chrome or LiveHTTPHeaders for Firefox) is valuable. Using a web proxy like <A href="https://portswigger.net/burp/" target="_top">Burp</A> or <A href="https://www.owasp.org/index.php/OWASP_Zed_Attack_Proxy_Project" target="_top">ZAP</A> may be helpful in creating or modifying requests. In <B>white-box hacking,</B> you have access to the source code and can use automated or manual analysis to identify bugs. You can treat Gruyere as if it's open source: you can read through the source code to try to find bugs. Gruyere is written in Python, so some familiarity with Python can be helpful. However, the security vulnerabilities covered are not Python-specific and you can do most of the lab without even looking at the code. You can run a local instance of Gruyere to assist in your hacking: for example, you can create an administrator account on your local instance to learn how administrative features work and then apply that knowledge to the instance you want to hack. Security researchers use both hacking techniques, often in combination, in real life. </P> <BR clear="left"> We'll tag each challenge to indicate which techniques are required to solve them: <BR><BR> <IMG src="/static/cheese_b.png" style="vertical-align:middle; padding-right: 5px;"> Challenges that can be solved just by using black box techniques.<BR><BR> <IMG src="/static/cheese_w.png" style="vertical-align:middle; padding-right: 5px;"> Challenges that require that you look at the Gruyere source code.<BR><BR> <IMG src="/static/cheese_bw.png" style="vertical-align:middle; padding-right: 5px;"> Challenges that require some specific knowledge of Gruyere that will be given in the first hint. <BR> <P style="color:red"> <B>WARNING:</B> Accessing or attacking a computer system without authorization is illegal in many jurisdictions. While doing this codelab, you are specifically granted authorization to attack the Gruyere application as directed. You may not attack Gruyere in ways other than described in this codelab, nor may you attack App Engine directly or any other Google service. You should use what you learn from the codelab to make your own applications more secure. You should not use it to attack any applications other than your own, and only do that with permission from the appropriate authorities (e.g., your company's security team). </P> <P></P> <FONT SIZE="+2"> <A href="/part1">Continue >></A> </FONT><BR> <BR> <P style="font-size:x-small"> © Google 2017 <A href="https://www.google.com/intl/en/policies/terms/">Terms of Service</A> <BR>The code portions of this codelab are licensed under the Creative Commons Attribution-No Derivative Works 3.0 United States license <<A href="https://creativecommons.org/licenses/by-nd/3.0/us/">https://creativecommons.org/licenses/by-nd/3.0/us</A>>. Brief excerpts of the code may be used for educational or instructional purposes provided this notice is kept intact. Except as otherwise noted the remainder of this codelab is licensed under the Creative Commons Attribution 3.0 United States license <<A href="https://creativecommons.org/licenses/by/3.0/us/">https://creativecommons.org/licenses/by/3.0/us</A>>. </P> </BODY></HTML> |
| URL | http://google-gruyere.appspot.com/0 |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 309 bytes. |
GET http://google-gruyere.appspot.com/0 HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/robots.txt |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 242 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: 31acf39338cca4c13a8ac0ebd8b4f695 Date: Tue, 06 Feb 2024 17:42:17 GMT Server: Google Frontend Content-Length: 359 |
| Response Body - size: 359 bytes. |
<HTML>
<STYLE> body, th, td, form { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; } h1 { color: #dd0000; } </STYLE> <TITLE>Gruyere Error</TITLE> <BODY> <H1>Gruyere Error</H1> That instance does not exist. <H2><A href="/">Home</A></H2> <H2><A href="/start">Start</A></H2> </BODY></HTML> |
| URL | http://google-gruyere.appspot.com/1 |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 309 bytes. |
GET http://google-gruyere.appspot.com/1 HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/robots.txt |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 242 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: 2de0e739188bfb4c756cf04d66dc15e4 Date: Tue, 06 Feb 2024 17:42:17 GMT Server: Google Frontend Content-Length: 359 |
| Response Body - size: 359 bytes. |
<HTML>
<STYLE> body, th, td, form { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; } h1 { color: #dd0000; } </STYLE> <TITLE>Gruyere Error</TITLE> <BODY> <H1>Gruyere Error</H1> That instance does not exist. <H2><A href="/">Home</A></H2> <H2><A href="/start">Start</A></H2> </BODY></HTML> |
| URL | http://google-gruyere.appspot.com/2 |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 309 bytes. |
GET http://google-gruyere.appspot.com/2 HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/robots.txt |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 242 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: 7eca5fc23f8b9884f93ce97b570b8646 Date: Tue, 06 Feb 2024 17:42:17 GMT Server: Google Frontend Content-Length: 359 |
| Response Body - size: 359 bytes. |
<HTML>
<STYLE> body, th, td, form { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; } h1 { color: #dd0000; } </STYLE> <TITLE>Gruyere Error</TITLE> <BODY> <H1>Gruyere Error</H1> That instance does not exist. <H2><A href="/">Home</A></H2> <H2><A href="/start">Start</A></H2> </BODY></HTML> |
| URL | http://google-gruyere.appspot.com/3 |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 309 bytes. |
GET http://google-gruyere.appspot.com/3 HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/robots.txt |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 242 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: 9ca17eedf0e86ef13ce8d268daca12dc Date: Tue, 06 Feb 2024 17:42:17 GMT Server: Google Frontend Content-Length: 359 |
| Response Body - size: 359 bytes. |
<HTML>
<STYLE> body, th, td, form { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; } h1 { color: #dd0000; } </STYLE> <TITLE>Gruyere Error</TITLE> <BODY> <H1>Gruyere Error</H1> That instance does not exist. <H2><A href="/">Home</A></H2> <H2><A href="/start">Start</A></H2> </BODY></HTML> |
| URL | http://google-gruyere.appspot.com/382665580745386307547168512335551204731/ |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 437 bytes. |
GET http://google-gruyere.appspot.com/382665580745386307547168512335551204731/ HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/382665580745386307547168512335551204731 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 249 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: afb44ad6606035ae60709b681130064c Date: Tue, 06 Feb 2024 17:42:21 GMT Server: Google Frontend Content-Length: 3480 |
| Response Body - size: 3,480 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Home</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> <script src="/382665580745386307547168512335551204731/lib.js" text="text/javascript"> </script> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/382665580745386307547168512335551204731/'>Home</a> </span> <span id='menu-right'> <a href='/382665580745386307547168512335551204731/login'>Sign in</a> | <a href='/382665580745386307547168512335551204731/newaccount.gtl'>Sign up</a> </span> </div> <div> <h2 class='has-refresh'>Gruyere: Home</h2> <div class='refresh'><a class='button' onclick='_refreshHome("382665580745386307547168512335551204731")' href='#'>Refresh</a></div> </div> <div class='content'> <table width='100%'> <tr><td colspan='3'><b>Most recent snippets:</b></td></tr> <tr> <td> </td> <td> <b><span style='color:blue'>Cheddar Mac</span></b> </td> <td width='100%'><span id='cheddar'>Gruyere is the cheesiest application on the web.</span> <br> <a href='/382665580745386307547168512335551204731/snippets.gtl?uid=cheddar'>All snippets</a> <a href='https://images.google.com/?q=cheddar+cheese'>Homepage</a> <br> <br> </td> </tr> <tr> <td> </td> <td> <b><span style='color:red; text-decoration:underline'>Brie</span></b> </td> <td width='100%'><span id='brie'>Brie is the queen of the cheeses<span style=color:red>!!!</span></span> <br> <a href='/382665580745386307547168512335551204731/snippets.gtl?uid=brie'>All snippets</a> <a href='https://news.google.com/news/search?q=brie'>Homepage</a> <br> <br> </td> </tr> </table> </div> </body> </html> |
| URL | http://google-gruyere.appspot.com/382665580745386307547168512335551204731/login |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 443 bytes. |
GET http://google-gruyere.appspot.com/382665580745386307547168512335551204731/login HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/382665580745386307547168512335551204731/ Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 249 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: 16b439f06534331a20b8802d36a3b6fa Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 2591 |
| Response Body - size: 2,591 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Login</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/382665580745386307547168512335551204731/'>Home</a> </span> <span id='menu-right'> <a href='/382665580745386307547168512335551204731/login'>Sign in</a> | <a href='/382665580745386307547168512335551204731/newaccount.gtl'>Sign up</a> </span> </div> <div> <h2>Gruyere: Login</h2> </div> <div class='content'> <form method='get' action='/382665580745386307547168512335551204731/login'> <table><tr><td> User name: </td><td> <input type='text' name='uid'> </td></tr><tr><td> Password: </td><td> <input type='password' name='pw'> </td></tr><tr><td></td><td align='right'> <input type='submit' value='Login'> </td></tr></table> </form> </div> </body> </html> |
| URL | http://google-gruyere.appspot.com/382665580745386307547168512335551204731/login?pw=ZAP&uid=ZAP |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 463 bytes. |
GET http://google-gruyere.appspot.com/382665580745386307547168512335551204731/login?pw=ZAP&uid=ZAP HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/382665580745386307547168512335551204731/login Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 249 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: 1b0b3f007d98bdce40097f85480aebda Date: Tue, 06 Feb 2024 17:42:25 GMT Server: Google Frontend Content-Length: 2650 |
| Response Body - size: 2,650 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Login</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/382665580745386307547168512335551204731/'>Home</a> </span> <span id='menu-right'> <a href='/382665580745386307547168512335551204731/login'>Sign in</a> | <a href='/382665580745386307547168512335551204731/newaccount.gtl'>Sign up</a> </span> </div> <div> <h2>Gruyere: Login</h2> </div> <div class='message'>Invalid user name or password.</div> <div class='content'> <form method='get' action='/382665580745386307547168512335551204731/login'> <table><tr><td> User name: </td><td> <input type='text' name='uid'> </td></tr><tr><td> Password: </td><td> <input type='password' name='pw'> </td></tr><tr><td></td><td align='right'> <input type='submit' value='Login'> </td></tr></table> </form> </div> </body> </html> |
| URL | http://google-gruyere.appspot.com/382665580745386307547168512335551204731/newaccount.gtl |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 452 bytes. |
GET http://google-gruyere.appspot.com/382665580745386307547168512335551204731/newaccount.gtl HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/382665580745386307547168512335551204731/ Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 249 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: 81fd781cfd9ee13ea54b5718c50baeed Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 2961 |
| Response Body - size: 2,961 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Profile</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/382665580745386307547168512335551204731/'>Home</a> </span> <span id='menu-right'> <a href='/382665580745386307547168512335551204731/login'>Sign in</a> | <a href='/382665580745386307547168512335551204731/newaccount.gtl'>Sign up</a> </span> </div> <h2>Gruyere: Sign up</h2> <div class='content'> <h3>Sign up for a new account.</h3> <form method='get' action='/382665580745386307547168512335551204731/saveprofile'> <input type='hidden' name='action' value='new'> <table><tr><td> User name: </td><td> <input type='text' name='uid' value='' maxlength='16'> </td></tr> <tr><td> Password: </td><td> <input type='password' name='pw'> <br><span style="color:red"><b>WARNING: Gruyere is not secure.<br> Do not use a password that you use for any real service.<br> Do not upload any personal or private data.</b></span> </td></tr> <input type='hidden' name='is_author' value='True'> <tr><td></td><td align='right'> <input type='submit' value='Create account'> </td></tr> </table> </form> </div> </body> </html> |
| URL | http://google-gruyere.appspot.com/382665580745386307547168512335551204731/saveprofile?action=new&is_author=True&pw=ZAP&uid=ZAP |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 504 bytes. |
GET http://google-gruyere.appspot.com/382665580745386307547168512335551204731/saveprofile?action=new&is_author=True&pw=ZAP&uid=ZAP HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/382665580745386307547168512335551204731/newaccount.gtl Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 378 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache Set-Cookie: GRUYERE=26087470|ZAP||author; path=/382665580745386307547168512335551204731 X-XSS-Protection: 0 X-Cloud-Trace-Context: 89a603bf4f00caebb0a57d884f1f61e1 Date: Tue, 06 Feb 2024 17:42:25 GMT Server: Google Frontend Content-Length: 2224 Expires: Tue, 06 Feb 2024 17:42:25 GMT |
| Response Body - size: 2,224 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Error</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/382665580745386307547168512335551204731/'>Home</a> </span> <span id='menu-right'> <a href='/382665580745386307547168512335551204731/login'>Sign in</a> | <a href='/382665580745386307547168512335551204731/newaccount.gtl'>Sign up</a> </span> </div> <div class='message'>Account created.</div> </body> </html> |
| URL | http://google-gruyere.appspot.com/382665580745386307547168512335551204731/snippets.gtl?uid=brie |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 459 bytes. |
GET http://google-gruyere.appspot.com/382665580745386307547168512335551204731/snippets.gtl?uid=brie HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/382665580745386307547168512335551204731/ Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 249 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: 572637f34b475afec4702be8a9453c2f Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 3059 |
| Response Body - size: 3,059 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Snippets</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> <script src="/382665580745386307547168512335551204731/lib.js" text="text/javascript"> </script> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/382665580745386307547168512335551204731/'>Home</a> </span> <span id='menu-right'> <a href='/382665580745386307547168512335551204731/login'>Sign in</a> | <a href='/382665580745386307547168512335551204731/newaccount.gtl'>Sign up</a> </span> </div> <div> <h2 class='has-refresh' id="user_name"> Brie </h2> <div class='refresh'><a class='button' onclick='_refreshSnippets("382665580745386307547168512335551204731", "brie")' href='#'>Refresh</a></div> <div class='content'> <table> <tr><td colspan='2'><b>All snippets:</b></td></tr> <tr> <td valign='top'> <script>document.write(0 + 1)</script> </td> <td valign='top'> <div id='0'> Brie is the queen of the cheeses<span style=color:red>!!!</span> </div> </td> </tr> </table> <br> <a href='https://news.google.com/news/search?q=brie'>Brie's site</a> </div> </body> </html> |
| URL | http://google-gruyere.appspot.com/382665580745386307547168512335551204731/snippets.gtl?uid=cheddar |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 462 bytes. |
GET http://google-gruyere.appspot.com/382665580745386307547168512335551204731/snippets.gtl?uid=cheddar HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/382665580745386307547168512335551204731/ Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 249 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: 68a11750c5367b4fae4aa3742510fd60 Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 3379 |
| Response Body - size: 3,379 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Snippets</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> <script src="/382665580745386307547168512335551204731/lib.js" text="text/javascript"> </script> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/382665580745386307547168512335551204731/'>Home</a> </span> <span id='menu-right'> <a href='/382665580745386307547168512335551204731/login'>Sign in</a> | <a href='/382665580745386307547168512335551204731/newaccount.gtl'>Sign up</a> </span> </div> <div> <h2 class='has-refresh' id="user_name"> Cheddar Mac </h2> <div class='refresh'><a class='button' onclick='_refreshSnippets("382665580745386307547168512335551204731", "cheddar")' href='#'>Refresh</a></div> <div class='content'> <table> <tr><td colspan='2'><b>All snippets:</b></td></tr> <tr> <td valign='top'> <script>document.write(0 + 1)</script> </td> <td valign='top'> <div id='0'> Gruyere is the cheesiest application on the web. </div> </td> </tr> <tr> <td valign='top'> <script>document.write(1 + 1)</script> </td> <td valign='top'> <div id='1'> I wonder if there are any security holes in this.... </div> </td> </tr> </table> <br> <a href='https://images.google.com/?q=cheddar+cheese'>Cheddar Mac's site</a> </div> </body> </html> |
| URL | http://google-gruyere.appspot.com/397587545265662818066921574239585949762/ |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 293 bytes. |
GET http://google-gruyere.appspot.com/397587545265662818066921574239585949762/ HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 249 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: a28a257ef79b06cf248b909efdc7c4c9 Date: Tue, 06 Feb 2024 17:42:17 GMT Server: Google Frontend Content-Length: 3480 |
| Response Body - size: 3,480 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Home</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> <script src="/397587545265662818066921574239585949762/lib.js" text="text/javascript"> </script> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/397587545265662818066921574239585949762/'>Home</a> </span> <span id='menu-right'> <a href='/397587545265662818066921574239585949762/login'>Sign in</a> | <a href='/397587545265662818066921574239585949762/newaccount.gtl'>Sign up</a> </span> </div> <div> <h2 class='has-refresh'>Gruyere: Home</h2> <div class='refresh'><a class='button' onclick='_refreshHome("397587545265662818066921574239585949762")' href='#'>Refresh</a></div> </div> <div class='content'> <table width='100%'> <tr><td colspan='3'><b>Most recent snippets:</b></td></tr> <tr> <td> </td> <td> <b><span style='color:blue'>Cheddar Mac</span></b> </td> <td width='100%'><span id='cheddar'>Gruyere is the cheesiest application on the web.</span> <br> <a href='/397587545265662818066921574239585949762/snippets.gtl?uid=cheddar'>All snippets</a> <a href='https://images.google.com/?q=cheddar+cheese'>Homepage</a> <br> <br> </td> </tr> <tr> <td> </td> <td> <b><span style='color:red; text-decoration:underline'>Brie</span></b> </td> <td width='100%'><span id='brie'>Brie is the queen of the cheeses<span style=color:red>!!!</span></span> <br> <a href='/397587545265662818066921574239585949762/snippets.gtl?uid=brie'>All snippets</a> <a href='https://news.google.com/news/search?q=brie'>Homepage</a> <br> <br> </td> </tr> </table> </div> </body> </html> |
| URL | http://google-gruyere.appspot.com/397587545265662818066921574239585949762/login |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 383 bytes. |
GET http://google-gruyere.appspot.com/397587545265662818066921574239585949762/login HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/397587545265662818066921574239585949762/ |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 249 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: 9083b154ecc26c292a9c737c8fa7a655 Date: Tue, 06 Feb 2024 17:42:17 GMT Server: Google Frontend Content-Length: 2591 |
| Response Body - size: 2,591 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Login</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/397587545265662818066921574239585949762/'>Home</a> </span> <span id='menu-right'> <a href='/397587545265662818066921574239585949762/login'>Sign in</a> | <a href='/397587545265662818066921574239585949762/newaccount.gtl'>Sign up</a> </span> </div> <div> <h2>Gruyere: Login</h2> </div> <div class='content'> <form method='get' action='/397587545265662818066921574239585949762/login'> <table><tr><td> User name: </td><td> <input type='text' name='uid'> </td></tr><tr><td> Password: </td><td> <input type='password' name='pw'> </td></tr><tr><td></td><td align='right'> <input type='submit' value='Login'> </td></tr></table> </form> </div> </body> </html> |
| URL | http://google-gruyere.appspot.com/397587545265662818066921574239585949762/login?pw=ZAP&uid=ZAP |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 463 bytes. |
GET http://google-gruyere.appspot.com/397587545265662818066921574239585949762/login?pw=ZAP&uid=ZAP HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/397587545265662818066921574239585949762/login Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 249 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: 9899d637fd9ca7dddfd483f9210096d5 Date: Tue, 06 Feb 2024 17:42:18 GMT Server: Google Frontend Content-Length: 2650 |
| Response Body - size: 2,650 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Login</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/397587545265662818066921574239585949762/'>Home</a> </span> <span id='menu-right'> <a href='/397587545265662818066921574239585949762/login'>Sign in</a> | <a href='/397587545265662818066921574239585949762/newaccount.gtl'>Sign up</a> </span> </div> <div> <h2>Gruyere: Login</h2> </div> <div class='message'>Invalid user name or password.</div> <div class='content'> <form method='get' action='/397587545265662818066921574239585949762/login'> <table><tr><td> User name: </td><td> <input type='text' name='uid'> </td></tr><tr><td> Password: </td><td> <input type='password' name='pw'> </td></tr><tr><td></td><td align='right'> <input type='submit' value='Login'> </td></tr></table> </form> </div> </body> </html> |
| URL | http://google-gruyere.appspot.com/397587545265662818066921574239585949762/newaccount.gtl |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 392 bytes. |
GET http://google-gruyere.appspot.com/397587545265662818066921574239585949762/newaccount.gtl HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/397587545265662818066921574239585949762/ |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 249 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: a9898974a14997c3d97dfe9c098c8645 Date: Tue, 06 Feb 2024 17:42:18 GMT Server: Google Frontend Content-Length: 2961 |
| Response Body - size: 2,961 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Profile</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/397587545265662818066921574239585949762/'>Home</a> </span> <span id='menu-right'> <a href='/397587545265662818066921574239585949762/login'>Sign in</a> | <a href='/397587545265662818066921574239585949762/newaccount.gtl'>Sign up</a> </span> </div> <h2>Gruyere: Sign up</h2> <div class='content'> <h3>Sign up for a new account.</h3> <form method='get' action='/397587545265662818066921574239585949762/saveprofile'> <input type='hidden' name='action' value='new'> <table><tr><td> User name: </td><td> <input type='text' name='uid' value='' maxlength='16'> </td></tr> <tr><td> Password: </td><td> <input type='password' name='pw'> <br><span style="color:red"><b>WARNING: Gruyere is not secure.<br> Do not use a password that you use for any real service.<br> Do not upload any personal or private data.</b></span> </td></tr> <input type='hidden' name='is_author' value='True'> <tr><td></td><td align='right'> <input type='submit' value='Create account'> </td></tr> </table> </form> </div> </body> </html> |
| URL | http://google-gruyere.appspot.com/397587545265662818066921574239585949762/saveprofile?action=new&is_author=True&pw=ZAP&uid=ZAP |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 504 bytes. |
GET http://google-gruyere.appspot.com/397587545265662818066921574239585949762/saveprofile?action=new&is_author=True&pw=ZAP&uid=ZAP HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/397587545265662818066921574239585949762/newaccount.gtl Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 378 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache Set-Cookie: GRUYERE=26087470|ZAP||author; path=/397587545265662818066921574239585949762 X-XSS-Protection: 0 X-Cloud-Trace-Context: 88931a38df2e52e671b2c4f9f074850c Date: Tue, 06 Feb 2024 17:42:18 GMT Server: Google Frontend Content-Length: 2224 Expires: Tue, 06 Feb 2024 17:42:18 GMT |
| Response Body - size: 2,224 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Error</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/397587545265662818066921574239585949762/'>Home</a> </span> <span id='menu-right'> <a href='/397587545265662818066921574239585949762/login'>Sign in</a> | <a href='/397587545265662818066921574239585949762/newaccount.gtl'>Sign up</a> </span> </div> <div class='message'>Account created.</div> </body> </html> |
| URL | http://google-gruyere.appspot.com/397587545265662818066921574239585949762/snippets.gtl?uid=brie |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 459 bytes. |
GET http://google-gruyere.appspot.com/397587545265662818066921574239585949762/snippets.gtl?uid=brie HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/397587545265662818066921574239585949762/ Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 249 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: 580704941b98bc2503ec3725400623e3 Date: Tue, 06 Feb 2024 17:42:18 GMT Server: Google Frontend Content-Length: 3059 |
| Response Body - size: 3,059 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Snippets</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> <script src="/397587545265662818066921574239585949762/lib.js" text="text/javascript"> </script> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/397587545265662818066921574239585949762/'>Home</a> </span> <span id='menu-right'> <a href='/397587545265662818066921574239585949762/login'>Sign in</a> | <a href='/397587545265662818066921574239585949762/newaccount.gtl'>Sign up</a> </span> </div> <div> <h2 class='has-refresh' id="user_name"> Brie </h2> <div class='refresh'><a class='button' onclick='_refreshSnippets("397587545265662818066921574239585949762", "brie")' href='#'>Refresh</a></div> <div class='content'> <table> <tr><td colspan='2'><b>All snippets:</b></td></tr> <tr> <td valign='top'> <script>document.write(0 + 1)</script> </td> <td valign='top'> <div id='0'> Brie is the queen of the cheeses<span style=color:red>!!!</span> </div> </td> </tr> </table> <br> <a href='https://news.google.com/news/search?q=brie'>Brie's site</a> </div> </body> </html> |
| URL | http://google-gruyere.appspot.com/397587545265662818066921574239585949762/snippets.gtl?uid=cheddar |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 402 bytes. |
GET http://google-gruyere.appspot.com/397587545265662818066921574239585949762/snippets.gtl?uid=cheddar HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/397587545265662818066921574239585949762/ |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 249 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: a11c4b524f2923326c98c963afe3771c Date: Tue, 06 Feb 2024 17:42:18 GMT Server: Google Frontend Content-Length: 3379 |
| Response Body - size: 3,379 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Snippets</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> <script src="/397587545265662818066921574239585949762/lib.js" text="text/javascript"> </script> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/397587545265662818066921574239585949762/'>Home</a> </span> <span id='menu-right'> <a href='/397587545265662818066921574239585949762/login'>Sign in</a> | <a href='/397587545265662818066921574239585949762/newaccount.gtl'>Sign up</a> </span> </div> <div> <h2 class='has-refresh' id="user_name"> Cheddar Mac </h2> <div class='refresh'><a class='button' onclick='_refreshSnippets("397587545265662818066921574239585949762", "cheddar")' href='#'>Refresh</a></div> <div class='content'> <table> <tr><td colspan='2'><b>All snippets:</b></td></tr> <tr> <td valign='top'> <script>document.write(0 + 1)</script> </td> <td valign='top'> <div id='0'> Gruyere is the cheesiest application on the web. </div> </td> </tr> <tr> <td valign='top'> <script>document.write(1 + 1)</script> </td> <td valign='top'> <div id='1'> I wonder if there are any security holes in this.... </div> </td> </tr> </table> <br> <a href='https://images.google.com/?q=cheddar+cheese'>Cheddar Mac's site</a> </div> </body> </html> |
| URL | http://google-gruyere.appspot.com/4 |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 309 bytes. |
GET http://google-gruyere.appspot.com/4 HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/robots.txt |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 242 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: 6b6c1f5e5992f5526d08b5733ab01efa Date: Tue, 06 Feb 2024 17:42:17 GMT Server: Google Frontend Content-Length: 359 |
| Response Body - size: 359 bytes. |
<HTML>
<STYLE> body, th, td, form { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; } h1 { color: #dd0000; } </STYLE> <TITLE>Gruyere Error</TITLE> <BODY> <H1>Gruyere Error</H1> That instance does not exist. <H2><A href="/">Home</A></H2> <H2><A href="/start">Start</A></H2> </BODY></HTML> |
| URL | http://google-gruyere.appspot.com/5 |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 309 bytes. |
GET http://google-gruyere.appspot.com/5 HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/robots.txt |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 242 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: 9a098e9e493538fe45dc4b5b8bc86c90 Date: Tue, 06 Feb 2024 17:42:17 GMT Server: Google Frontend Content-Length: 359 |
| Response Body - size: 359 bytes. |
<HTML>
<STYLE> body, th, td, form { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; } h1 { color: #dd0000; } </STYLE> <TITLE>Gruyere Error</TITLE> <BODY> <H1>Gruyere Error</H1> That instance does not exist. <H2><A href="/">Home</A></H2> <H2><A href="/start">Start</A></H2> </BODY></HTML> |
| URL | http://google-gruyere.appspot.com/6 |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 309 bytes. |
GET http://google-gruyere.appspot.com/6 HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/robots.txt |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 242 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: ffd1d5503f0612cb50d92820b7e973c0 Date: Tue, 06 Feb 2024 17:42:17 GMT Server: Google Frontend Content-Length: 359 |
| Response Body - size: 359 bytes. |
<HTML>
<STYLE> body, th, td, form { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; } h1 { color: #dd0000; } </STYLE> <TITLE>Gruyere Error</TITLE> <BODY> <H1>Gruyere Error</H1> That instance does not exist. <H2><A href="/">Home</A></H2> <H2><A href="/start">Start</A></H2> </BODY></HTML> |
| URL | http://google-gruyere.appspot.com/7 |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 309 bytes. |
GET http://google-gruyere.appspot.com/7 HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/robots.txt |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 242 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: c4c66498c12d026492197ab71fb6f619 Date: Tue, 06 Feb 2024 17:42:17 GMT Server: Google Frontend Content-Length: 359 |
| Response Body - size: 359 bytes. |
<HTML>
<STYLE> body, th, td, form { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; } h1 { color: #dd0000; } </STYLE> <TITLE>Gruyere Error</TITLE> <BODY> <H1>Gruyere Error</H1> That instance does not exist. <H2><A href="/">Home</A></H2> <H2><A href="/start">Start</A></H2> </BODY></HTML> |
| URL | http://google-gruyere.appspot.com/8 |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 309 bytes. |
GET http://google-gruyere.appspot.com/8 HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/robots.txt |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 242 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: d267fca285d0f89f6f33f8e6dd1381b1 Date: Tue, 06 Feb 2024 17:42:17 GMT Server: Google Frontend Content-Length: 359 |
| Response Body - size: 359 bytes. |
<HTML>
<STYLE> body, th, td, form { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; } h1 { color: #dd0000; } </STYLE> <TITLE>Gruyere Error</TITLE> <BODY> <H1>Gruyere Error</H1> That instance does not exist. <H2><A href="/">Home</A></H2> <H2><A href="/start">Start</A></H2> </BODY></HTML> |
| URL | http://google-gruyere.appspot.com/9 |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 309 bytes. |
GET http://google-gruyere.appspot.com/9 HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/robots.txt |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 242 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: c607b5cf2b147b18d0d00d483fec7990 Date: Tue, 06 Feb 2024 17:42:17 GMT Server: Google Frontend Content-Length: 359 |
| Response Body - size: 359 bytes. |
<HTML>
<STYLE> body, th, td, form { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; } h1 { color: #dd0000; } </STYLE> <TITLE>Gruyere Error</TITLE> <BODY> <H1>Gruyere Error</H1> That instance does not exist. <H2><A href="/">Home</A></H2> <H2><A href="/start">Start</A></H2> </BODY></HTML> |
| URL | http://google-gruyere.appspot.com/code/ |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 368 bytes. |
GET http://google-gruyere.appspot.com/code/ HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/part1 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 242 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: a2ac29924b94533ea4fb5dd794718202 Date: Tue, 06 Feb 2024 17:42:21 GMT Server: Google Frontend Content-Length: 354 |
| Response Body - size: 354 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN"
"https://www.w3.org/TR/html4/frameset.dtd"> <HTML> <HEAD> <TITLE>Gruyere Code</TITLE> </HEAD> <FRAMESET cols="165,100%"> <FRAME src="/static/codeindex.html"> <FRAME name="codepage" src=""> <NOFRAMES> <A href="/static/codeindex/html">Code Index</A> </NOFRAMES> </FRAMESET> </HTML> |
| URL | http://google-gruyere.appspot.com/code/?data.py |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 376 bytes. |
GET http://google-gruyere.appspot.com/code/?data.py HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/part1 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 242 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: dd7164b7280debdede68a6e298f79233 Date: Tue, 06 Feb 2024 17:42:21 GMT Server: Google Frontend Content-Length: 367 |
| Response Body - size: 367 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN"
"https://www.w3.org/TR/html4/frameset.dtd"> <HTML> <HEAD> <TITLE>Gruyere Code</TITLE> </HEAD> <FRAMESET cols="165,100%"> <FRAME src="/static/codeindex.html"> <FRAME name="codepage" src="/code/data.py"> <NOFRAMES> <A href="/static/codeindex/html">Code Index</A> </NOFRAMES> </FRAMESET> </HTML> |
| URL | http://google-gruyere.appspot.com/code/?gruyere.py |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 379 bytes. |
GET http://google-gruyere.appspot.com/code/?gruyere.py HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/part1 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 242 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: 5586748370261caee1bd94c9469b4724 Date: Tue, 06 Feb 2024 17:42:21 GMT Server: Google Frontend Content-Length: 370 |
| Response Body - size: 370 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN"
"https://www.w3.org/TR/html4/frameset.dtd"> <HTML> <HEAD> <TITLE>Gruyere Code</TITLE> </HEAD> <FRAMESET cols="165,100%"> <FRAME src="/static/codeindex.html"> <FRAME name="codepage" src="/code/gruyere.py"> <NOFRAMES> <A href="/static/codeindex/html">Code Index</A> </NOFRAMES> </FRAMESET> </HTML> |
| URL | http://google-gruyere.appspot.com/code/?gtl.py |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 375 bytes. |
GET http://google-gruyere.appspot.com/code/?gtl.py HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/part1 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 242 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: a71b76cc1d5c4be2212e2fc0ab83c53b Date: Tue, 06 Feb 2024 17:42:22 GMT Server: Google Frontend Content-Length: 366 |
| Response Body - size: 366 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN"
"https://www.w3.org/TR/html4/frameset.dtd"> <HTML> <HEAD> <TITLE>Gruyere Code</TITLE> </HEAD> <FRAMESET cols="165,100%"> <FRAME src="/static/codeindex.html"> <FRAME name="codepage" src="/code/gtl.py"> <NOFRAMES> <A href="/static/codeindex/html">Code Index</A> </NOFRAMES> </FRAMESET> </HTML> |
| URL | http://google-gruyere.appspot.com/code/?resoources/dump.gtl |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 388 bytes. |
GET http://google-gruyere.appspot.com/code/?resoources/dump.gtl HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/part5 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 242 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: e5ef295042883012b5ba3222bc3ac891 Date: Tue, 06 Feb 2024 17:42:23 GMT Server: Google Frontend Content-Length: 354 |
| Response Body - size: 354 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN"
"https://www.w3.org/TR/html4/frameset.dtd"> <HTML> <HEAD> <TITLE>Gruyere Code</TITLE> </HEAD> <FRAMESET cols="165,100%"> <FRAME src="/static/codeindex.html"> <FRAME name="codepage" src=""> <NOFRAMES> <A href="/static/codeindex/html">Code Index</A> </NOFRAMES> </FRAMESET> </HTML> |
| URL | http://google-gruyere.appspot.com/code/?resources/dump.gtl |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 387 bytes. |
GET http://google-gruyere.appspot.com/code/?resources/dump.gtl HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/part5 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 242 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: 7b25ef14221c561f20db288ba5d3ced0 Date: Tue, 06 Feb 2024 17:42:23 GMT Server: Google Frontend Content-Length: 378 |
| Response Body - size: 378 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN"
"https://www.w3.org/TR/html4/frameset.dtd"> <HTML> <HEAD> <TITLE>Gruyere Code</TITLE> </HEAD> <FRAMESET cols="165,100%"> <FRAME src="/static/codeindex.html"> <FRAME name="codepage" src="/code/resources/dump.gtl"> <NOFRAMES> <A href="/static/codeindex/html">Code Index</A> </NOFRAMES> </FRAMESET> </HTML> |
| URL | http://google-gruyere.appspot.com/code/?resources/editprofile.gtl |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 394 bytes. |
GET http://google-gruyere.appspot.com/code/?resources/editprofile.gtl HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/part3 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 242 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: 329a33fddd017ee001084efe67d3396c Date: Tue, 06 Feb 2024 17:42:23 GMT Server: Google Frontend Content-Length: 385 |
| Response Body - size: 385 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN"
"https://www.w3.org/TR/html4/frameset.dtd"> <HTML> <HEAD> <TITLE>Gruyere Code</TITLE> </HEAD> <FRAMESET cols="165,100%"> <FRAME src="/static/codeindex.html"> <FRAME name="codepage" src="/code/resources/editprofile.gtl"> <NOFRAMES> <A href="/static/codeindex/html">Code Index</A> </NOFRAMES> </FRAMESET> </HTML> |
| URL | http://google-gruyere.appspot.com/code/?resources/error.gtl |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 388 bytes. |
GET http://google-gruyere.appspot.com/code/?resources/error.gtl HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/part2 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 242 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: da4f492d706e701689807f8475a10dec Date: Tue, 06 Feb 2024 17:42:23 GMT Server: Google Frontend Content-Length: 379 |
| Response Body - size: 379 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN"
"https://www.w3.org/TR/html4/frameset.dtd"> <HTML> <HEAD> <TITLE>Gruyere Code</TITLE> </HEAD> <FRAMESET cols="165,100%"> <FRAME src="/static/codeindex.html"> <FRAME name="codepage" src="/code/resources/error.gtl"> <NOFRAMES> <A href="/static/codeindex/html">Code Index</A> </NOFRAMES> </FRAMESET> </HTML> |
| URL | http://google-gruyere.appspot.com/code/?resources/feed.gtl |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 387 bytes. |
GET http://google-gruyere.appspot.com/code/?resources/feed.gtl HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/part1 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 242 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: 1c4bbde7a962153bb90c276f1875dd0b Date: Tue, 06 Feb 2024 17:42:22 GMT Server: Google Frontend Content-Length: 378 |
| Response Body - size: 378 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN"
"https://www.w3.org/TR/html4/frameset.dtd"> <HTML> <HEAD> <TITLE>Gruyere Code</TITLE> </HEAD> <FRAMESET cols="165,100%"> <FRAME src="/static/codeindex.html"> <FRAME name="codepage" src="/code/resources/feed.gtl"> <NOFRAMES> <A href="/static/codeindex/html">Code Index</A> </NOFRAMES> </FRAMESET> </HTML> |
| URL | http://google-gruyere.appspot.com/code/?resources/home.gtl |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 387 bytes. |
GET http://google-gruyere.appspot.com/code/?resources/home.gtl HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/part2 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 242 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: 5375fd055e641e63a7b3b7a869ce9082 Date: Tue, 06 Feb 2024 17:42:23 GMT Server: Google Frontend Content-Length: 378 |
| Response Body - size: 378 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN"
"https://www.w3.org/TR/html4/frameset.dtd"> <HTML> <HEAD> <TITLE>Gruyere Code</TITLE> </HEAD> <FRAMESET cols="165,100%"> <FRAME src="/static/codeindex.html"> <FRAME name="codepage" src="/code/resources/home.gtl"> <NOFRAMES> <A href="/static/codeindex/html">Code Index</A> </NOFRAMES> </FRAMESET> </HTML> |
| URL | http://google-gruyere.appspot.com/code/?resources/manage.gtl |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 389 bytes. |
GET http://google-gruyere.appspot.com/code/?resources/manage.gtl HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/part4 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 242 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: d11704238467f7d2308117ac035b9b43 Date: Tue, 06 Feb 2024 17:42:23 GMT Server: Google Frontend Content-Length: 380 |
| Response Body - size: 380 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN"
"https://www.w3.org/TR/html4/frameset.dtd"> <HTML> <HEAD> <TITLE>Gruyere Code</TITLE> </HEAD> <FRAMESET cols="165,100%"> <FRAME src="/static/codeindex.html"> <FRAME name="codepage" src="/code/resources/manage.gtl"> <NOFRAMES> <A href="/static/codeindex/html">Code Index</A> </NOFRAMES> </FRAMESET> </HTML> |
| URL | http://google-gruyere.appspot.com/code/?resources/menubar.gtl |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 390 bytes. |
GET http://google-gruyere.appspot.com/code/?resources/menubar.gtl HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/part4 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 242 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: a5e2f484f397f6415f3a4876284bb09e Date: Tue, 06 Feb 2024 17:42:23 GMT Server: Google Frontend Content-Length: 381 |
| Response Body - size: 381 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN"
"https://www.w3.org/TR/html4/frameset.dtd"> <HTML> <HEAD> <TITLE>Gruyere Code</TITLE> </HEAD> <FRAMESET cols="165,100%"> <FRAME src="/static/codeindex.html"> <FRAME name="codepage" src="/code/resources/menubar.gtl"> <NOFRAMES> <A href="/static/codeindex/html">Code Index</A> </NOFRAMES> </FRAMESET> </HTML> |
| URL | http://google-gruyere.appspot.com/code/?sanitize.py |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 380 bytes. |
GET http://google-gruyere.appspot.com/code/?sanitize.py HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/part1 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 242 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: e0dc5d0faacef6fef9cc9a73156dbc84 Date: Tue, 06 Feb 2024 17:42:22 GMT Server: Google Frontend Content-Length: 371 |
| Response Body - size: 371 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN"
"https://www.w3.org/TR/html4/frameset.dtd"> <HTML> <HEAD> <TITLE>Gruyere Code</TITLE> </HEAD> <FRAMESET cols="165,100%"> <FRAME src="/static/codeindex.html"> <FRAME name="codepage" src="/code/sanitize.py"> <NOFRAMES> <A href="/static/codeindex/html">Code Index</A> </NOFRAMES> </FRAMESET> </HTML> |
| URL | http://google-gruyere.appspot.com/code/data.py |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 383 bytes. |
GET http://google-gruyere.appspot.com/code/data.py HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/code/?data.py Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 243 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: b152b8ac64ff7ccb7b932ffaf58aa6d5 Date: Tue, 06 Feb 2024 17:42:23 GMT Server: Google Frontend Content-Length: 4190 |
| Response Body - size: 4,190 bytes. |
<HTML>
<STYLE> SMALL { color: #AAAAFF; font-size: 75% } BIG { color: #3333FF; font-size: 150%; font-weight: bold} </STYLE><BODY><PRE> <BIG>data.py</BIG> <SMALL> 1 </SMALL>"""Gruyere - Default data for Gruyere, a web application with holes. <SMALL> 2 </SMALL> <SMALL> 3 </SMALL>Copyright 2017 Google Inc. All rights reserved. <SMALL> 4 </SMALL> <SMALL> 5 </SMALL>This code is licensed under the https://creativecommons.org/licenses/by-nd/3.0/us/ <SMALL> 6 </SMALL>Creative Commons Attribution-No Derivative Works 3.0 United States license. <SMALL> 7 </SMALL> <SMALL> 8 </SMALL>DO NOT COPY THIS CODE! <SMALL> 9 </SMALL> <SMALL> 10 </SMALL>This application is a small self-contained web application with numerous <SMALL> 11 </SMALL>security holes. It is provided for use with the Web Application Exploits and <SMALL> 12 </SMALL>Defenses codelab. You may modify the code for your own use while doing the <SMALL> 13 </SMALL>codelab but you may not distribute the modified code. Brief excerpts of this <SMALL> 14 </SMALL>code may be used for educational or instructional purposes provided this <SMALL> 15 </SMALL>notice is kept intact. By using Gruyere you agree to the Terms of Service <SMALL> 16 </SMALL>https://www.google.com/intl/en/policies/terms/ <SMALL> 17 </SMALL>""" <SMALL> 18 </SMALL> <SMALL> 19 </SMALL>__author__ = 'Bruce Leban' <SMALL> 20 </SMALL> <SMALL> 21 </SMALL># system modules <SMALL> 22 </SMALL>import copy <SMALL> 23 </SMALL> <SMALL> 24 </SMALL>DEFAULT_DATA = { <SMALL> 25 </SMALL> 'administrator': { <SMALL> 26 </SMALL> 'name': 'Admin', <SMALL> 27 </SMALL> 'pw': 'secret', <SMALL> 28 </SMALL> 'is_author': False, <SMALL> 29 </SMALL> 'is_admin': True, <SMALL> 30 </SMALL> 'private_snippet': 'My password is secret. Get it?', <SMALL> 31 </SMALL> 'web_site': 'https://www.google.com/contact/', <SMALL> 32 </SMALL> }, <SMALL> 33 </SMALL> 'cheddar': { <SMALL> 34 </SMALL> 'name': 'Cheddar Mac', <SMALL> 35 </SMALL> 'pw': 'orange', <SMALL> 36 </SMALL> 'is_author': True, <SMALL> 37 </SMALL> 'is_admin': False, <SMALL> 38 </SMALL> 'private_snippet': 'My SSN is <a href="https://www.google.com/' + <SMALL> 39 </SMALL> 'search?q=078-05-1120">078-05-1120</a>.', <SMALL> 40 </SMALL> 'web_site': 'https://images.google.com/?q=cheddar+cheese', <SMALL> 41 </SMALL> 'color': 'blue', <SMALL> 42 </SMALL> 'snippets': [ <SMALL> 43 </SMALL> 'Gruyere is the cheesiest application on the web.', <SMALL> 44 </SMALL> 'I wonder if there are any security holes in this....' <SMALL> 45 </SMALL> ], <SMALL> 46 </SMALL> }, <SMALL> 47 </SMALL> 'sardo': { <SMALL> 48 </SMALL> 'name': 'Miss Sardo', <SMALL> 49 </SMALL> 'pw': 'odras', <SMALL> 50 </SMALL> 'is_author': True, <SMALL> 51 </SMALL> 'is_admin': False, <SMALL> 52 </SMALL> 'private_snippet': 'I hate my brother Romano.', <SMALL> 53 </SMALL> 'web_site': 'https://www.google.com/search?q="pecorino+sardo"', <SMALL> 54 </SMALL> 'color': 'red', <SMALL> 55 </SMALL> 'snippets': [], <SMALL> 56 </SMALL> }, <SMALL> 57 </SMALL> 'brie': { <SMALL> 58 </SMALL> 'name': 'Brie', <SMALL> 59 </SMALL> 'pw': 'briebrie', <SMALL> 60 </SMALL> 'is_author': True, <SMALL> 61 </SMALL> 'is_admin': False, <SMALL> 62 </SMALL> 'private_snippet': 'I use the same password for all my accounts.', <SMALL> 63 </SMALL> 'web_site': 'https://news.google.com/news/search?q=brie', <SMALL> 64 </SMALL> 'color': 'red; text-decoration:underline', <SMALL> 65 </SMALL> 'snippets': [ <SMALL> 66 </SMALL> 'Brie is the queen of the cheeses<span style=color:red>!!!</span>' <SMALL> 67 </SMALL> ], <SMALL> 68 </SMALL> }, <SMALL> 69 </SMALL>} <SMALL> 70 </SMALL> <SMALL> 71 </SMALL> <SMALL> 72 </SMALL>def DefaultData(): <SMALL> 73 </SMALL> """Provides default data for Gruyere.""" <SMALL> 74 </SMALL> return copy.deepcopy(DEFAULT_DATA) </PRE></BODY></HTML> |
| URL | http://google-gruyere.appspot.com/code/gruyere.py |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 389 bytes. |
GET http://google-gruyere.appspot.com/code/gruyere.py HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/code/?gruyere.py Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 244 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: bb9a3355a53dd8a63ca7a9ba938eec94 Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 45208 |
| Response Body - size: 45,208 bytes. |
<HTML>
<STYLE> SMALL { color: #AAAAFF; font-size: 75% } BIG { color: #3333FF; font-size: 150%; font-weight: bold} </STYLE><BODY><PRE> <BIG>gruyere.py</BIG> <SMALL> 1 </SMALL>#!/usr/bin/env python2.7 <SMALL> 2 </SMALL> <SMALL> 3 </SMALL>"""Gruyere - a web application with holes. <SMALL> 4 </SMALL> <SMALL> 5 </SMALL>Copyright 2017 Google Inc. All rights reserved. <SMALL> 6 </SMALL> <SMALL> 7 </SMALL>This code is licensed under the <SMALL> 8 </SMALL>https://creativecommons.org/licenses/by-nd/3.0/us/ <SMALL> 9 </SMALL>Creative Commons Attribution-No Derivative Works 3.0 United States license. <SMALL> 10 </SMALL> <SMALL> 11 </SMALL>DO NOT COPY THIS CODE! <SMALL> 12 </SMALL> <SMALL> 13 </SMALL>This application is a small self-contained web application with numerous <SMALL> 14 </SMALL>security holes. It is provided for use with the Web Application Exploits and <SMALL> 15 </SMALL>Defenses codelab. You may modify the code for your own use while doing the <SMALL> 16 </SMALL>codelab but you may not distribute the modified code. Brief excerpts of this <SMALL> 17 </SMALL>code may be used for educational or instructional purposes provided this <SMALL> 18 </SMALL>notice is kept intact. By using Gruyere you agree to the Terms of Service <SMALL> 19 </SMALL>https://www.google.com/intl/en/policies/terms/ <SMALL> 20 </SMALL>""" <SMALL> 21 </SMALL> <SMALL> 22 </SMALL>__author__ = 'Bruce Leban' <SMALL> 23 </SMALL> <SMALL> 24 </SMALL># system modules <SMALL> 25 </SMALL>from BaseHTTPServer import BaseHTTPRequestHandler <SMALL> 26 </SMALL>from BaseHTTPServer import HTTPServer <SMALL> 27 </SMALL>import cgi <SMALL> 28 </SMALL>import cPickle <SMALL> 29 </SMALL>import os <SMALL> 30 </SMALL>import random <SMALL> 31 </SMALL>import sys <SMALL> 32 </SMALL>import threading <SMALL> 33 </SMALL>import urllib <SMALL> 34 </SMALL>from urlparse import urlparse <SMALL> 35 </SMALL> <SMALL> 36 </SMALL>try: <SMALL> 37 </SMALL> sys.dont_write_bytecode = True <SMALL> 38 </SMALL>except AttributeError: <SMALL> 39 </SMALL> pass <SMALL> 40 </SMALL> <SMALL> 41 </SMALL># our modules <SMALL> 42 </SMALL>import data <SMALL> 43 </SMALL>import gtl <SMALL> 44 </SMALL> <SMALL> 45 </SMALL> <SMALL> 46 </SMALL>DB_FILE = '/stored-data.txt' <SMALL> 47 </SMALL>SECRET_FILE = '/secret.txt' <SMALL> 48 </SMALL> <SMALL> 49 </SMALL>INSTALL_PATH = '.' <SMALL> 50 </SMALL>RESOURCE_PATH = 'resources' <SMALL> 51 </SMALL> <SMALL> 52 </SMALL>SPECIAL_COOKIE = '_cookie' <SMALL> 53 </SMALL>SPECIAL_PROFILE = '_profile' <SMALL> 54 </SMALL>SPECIAL_DB = '_db' <SMALL> 55 </SMALL>SPECIAL_PARAMS = '_params' <SMALL> 56 </SMALL>SPECIAL_UNIQUE_ID = '_unique_id' <SMALL> 57 </SMALL> <SMALL> 58 </SMALL>COOKIE_UID = 'uid' <SMALL> 59 </SMALL>COOKIE_ADMIN = 'is_admin' <SMALL> 60 </SMALL>COOKIE_AUTHOR = 'is_author' <SMALL> 61 </SMALL> <SMALL> 62 </SMALL> <SMALL> 63 </SMALL># Set to True to cause the server to exit after processing the current url. <SMALL> 64 </SMALL>quit_server = False <SMALL> 65 </SMALL> <SMALL> 66 </SMALL># A global copy of the database so that _GetDatabase can access it. <SMALL> 67 </SMALL>stored_data = None <SMALL> 68 </SMALL> <SMALL> 69 </SMALL># The HTTPServer object. <SMALL> 70 </SMALL>http_server = None <SMALL> 71 </SMALL> <SMALL> 72 </SMALL># A secret value used to generate hashes to protect cookies from tampering. <SMALL> 73 </SMALL>cookie_secret = '' <SMALL> 74 </SMALL> <SMALL> 75 </SMALL># File extensions of resource files that we recognize. <SMALL> 76 </SMALL>RESOURCE_CONTENT_TYPES = { <SMALL> 77 </SMALL> '.css': 'text/css', <SMALL> 78 </SMALL> '.gif': 'image/gif', <SMALL> 79 </SMALL> '.htm': 'text/html', <SMALL> 80 </SMALL> '.html': 'text/html', <SMALL> 81 </SMALL> '.js': 'application/javascript', <SMALL> 82 </SMALL> '.jpeg': 'image/jpeg', <SMALL> 83 </SMALL> '.jpg': 'image/jpeg', <SMALL> 84 </SMALL> '.png': 'image/png', <SMALL> 85 </SMALL> '.ico': 'image/x-icon', <SMALL> 86 </SMALL> '.text': 'text/plain', <SMALL> 87 </SMALL> '.txt': 'text/plain', <SMALL> 88 </SMALL>} <SMALL> 89 </SMALL> <SMALL> 90 </SMALL> <SMALL> 91 </SMALL>def main(): <SMALL> 92 </SMALL> _SetWorkingDirectory() <SMALL> 93 </SMALL> <SMALL> 94 </SMALL> global quit_server <SMALL> 95 </SMALL> quit_server = False <SMALL> 96 </SMALL> <SMALL> 97 </SMALL> # Normally, Gruyere only accepts connections to/from localhost. If you <SMALL> 98 </SMALL> # would like to allow access from other ip addresses, you can change to <SMALL> 99 </SMALL> # operate in a less secure mode. Set insecure_mode to True to serve on the <SMALL>100 </SMALL> # hostname instead of localhost and add the addresses of the other machines <SMALL>101 </SMALL> # to allowed_ips below. <SMALL>102 </SMALL> <SMALL>103 </SMALL> insecure_mode = False <SMALL>104 </SMALL> <SMALL>105 </SMALL> # WARNING! DO NOT CHANGE THE FOLLOWING SECTION OF CODE! <SMALL>106 </SMALL> <SMALL>107 </SMALL> # This application is very exploitable. It takes several precautions to <SMALL>108 </SMALL> # limit the risk from a real attacker: <SMALL>109 </SMALL> # (1) Serve requests on localhost so that it will not be accessible <SMALL>110 </SMALL> # from other machines. <SMALL>111 </SMALL> # (2) If a request is received from any IP other than localhost, quit. <SMALL>112 </SMALL> # (This protection is implemented in do_GET/do_POST.) <SMALL>113 </SMALL> # (3) Inject a random identifier as the first part of the path and <SMALL>114 </SMALL> # quit if a request is received without this identifier (except for an <SMALL>115 </SMALL> # empty path which redirects and /favicon.ico). <SMALL>116 </SMALL> # (4) Automatically exit after 2 hours (7200 seconds) to mitigate against <SMALL>117 </SMALL> # accidentally leaving the server running. <SMALL>118 </SMALL> <SMALL>119 </SMALL> quit_timer = threading.Timer(7200, lambda: _Exit('Timeout')) # DO NOT CHANGE <SMALL>120 </SMALL> quit_timer.start() # DO NOT CHANGE <SMALL>121 </SMALL> <SMALL>122 </SMALL> if insecure_mode: # DO NOT CHANGE <SMALL>123 </SMALL> server_name = os.popen('hostname').read().replace('\n', '') # DO NOT CHANGE <SMALL>124 </SMALL> else: # DO NOT CHANGE <SMALL>125 </SMALL> server_name = '127.0.0.1' # DO NOT CHANGE <SMALL>126 </SMALL> server_port = 8008 # DO NOT CHANGE <SMALL>127 </SMALL> <SMALL>128 </SMALL> # The unique id is created from a CSPRNG. <SMALL>129 </SMALL> try: # DO NOT CHANGE <SMALL>130 </SMALL> r = random.SystemRandom() # DO NOT CHANGE <SMALL>131 </SMALL> except NotImplementedError: # DO NOT CHANGE <SMALL>132 </SMALL> _Exit('Could not obtain a CSPRNG source') # DO NOT CHANGE <SMALL>133 </SMALL> <SMALL>134 </SMALL> global server_unique_id # DO NOT CHANGE <SMALL>135 </SMALL> server_unique_id = str(r.randint(2**128, 2**(128+1))) # DO NOT CHANGE <SMALL>136 </SMALL> <SMALL>137 </SMALL> # END WARNING! <SMALL>138 </SMALL> <SMALL>139 </SMALL> global http_server <SMALL>140 </SMALL> http_server = HTTPServer((server_name, server_port), <SMALL>141 </SMALL> GruyereRequestHandler) <SMALL>142 </SMALL> <SMALL>143 </SMALL> print >>sys.stderr, ''' <SMALL>144 </SMALL> Gruyere started... <SMALL>145 </SMALL> http://%s:%d/ <SMALL>146 </SMALL> http://%s:%d/%s/''' % ( <SMALL>147 </SMALL> server_name, server_port, server_name, server_port, <SMALL>148 </SMALL> server_unique_id) <SMALL>149 </SMALL> <SMALL>150 </SMALL> global stored_data <SMALL>151 </SMALL> stored_data = _LoadDatabase() <SMALL>152 </SMALL> <SMALL>153 </SMALL> while not quit_server: <SMALL>154 </SMALL> try: <SMALL>155 </SMALL> http_server.handle_request() <SMALL>156 </SMALL> _SaveDatabase(stored_data) <SMALL>157 </SMALL> except KeyboardInterrupt: <SMALL>158 </SMALL> print >>sys.stderr, '\nReceived KeyboardInterrupt' <SMALL>159 </SMALL> quit_server = True <SMALL>160 </SMALL> <SMALL>161 </SMALL> print >>sys.stderr, '\nClosing' <SMALL>162 </SMALL> http_server.socket.close() <SMALL>163 </SMALL> _Exit('quit_server') <SMALL>164 </SMALL> <SMALL>165 </SMALL> <SMALL>166 </SMALL>def _Exit(reason): <SMALL>167 </SMALL> # use os._exit instead of sys.exit because this can't be trapped <SMALL>168 </SMALL> print >>sys.stderr, '\nExit: ' + reason <SMALL>169 </SMALL> os._exit(0) <SMALL>170 </SMALL> <SMALL>171 </SMALL> <SMALL>172 </SMALL>def _SetWorkingDirectory(): <SMALL>173 </SMALL> """Set the working directory to the directory containing this file.""" <SMALL>174 </SMALL> if sys.path[0]: <SMALL>175 </SMALL> os.chdir(sys.path[0]) <SMALL>176 </SMALL> <SMALL>177 </SMALL> <SMALL>178 </SMALL>def _LoadDatabase(): <SMALL>179 </SMALL> """Load the database from stored-data.txt. <SMALL>180 </SMALL> <SMALL>181 </SMALL> Returns: <SMALL>182 </SMALL> The loaded database. <SMALL>183 </SMALL> """ <SMALL>184 </SMALL> <SMALL>185 </SMALL> try: <SMALL>186 </SMALL> f = _Open(INSTALL_PATH, DB_FILE) <SMALL>187 </SMALL> stored_data = cPickle.load(f) <SMALL>188 </SMALL> f.close() <SMALL>189 </SMALL> except (IOError, ValueError): <SMALL>190 </SMALL> _Log('Couldn\'t load data; expected the first time Gruyere is run') <SMALL>191 </SMALL> stored_data = None <SMALL>192 </SMALL> <SMALL>193 </SMALL> f = _Open(INSTALL_PATH, SECRET_FILE) <SMALL>194 </SMALL> global cookie_secret <SMALL>195 </SMALL> cookie_secret = f.readline() <SMALL>196 </SMALL> f.close() <SMALL>197 </SMALL> <SMALL>198 </SMALL> return stored_data <SMALL>199 </SMALL> <SMALL>200 </SMALL> <SMALL>201 </SMALL>def _SaveDatabase(save_database): <SMALL>202 </SMALL> """Save the database to stored-data.txt. <SMALL>203 </SMALL> <SMALL>204 </SMALL> Args: <SMALL>205 </SMALL> save_database: the database to save. <SMALL>206 </SMALL> """ <SMALL>207 </SMALL> <SMALL>208 </SMALL> try: <SMALL>209 </SMALL> f = _Open(INSTALL_PATH, DB_FILE, 'w') <SMALL>210 </SMALL> cPickle.dump(save_database, f) <SMALL>211 </SMALL> f.close() <SMALL>212 </SMALL> except IOError: <SMALL>213 </SMALL> _Log('Couldn\'t save data') <SMALL>214 </SMALL> <SMALL>215 </SMALL> <SMALL>216 </SMALL>def _Open(location, filename, mode='rb'): <SMALL>217 </SMALL> """Open a file from a specific location. <SMALL>218 </SMALL> <SMALL>219 </SMALL> Args: <SMALL>220 </SMALL> location: The directory containing the file. <SMALL>221 </SMALL> filename: The name of the file. <SMALL>222 </SMALL> mode: File mode for open(). <SMALL>223 </SMALL> <SMALL>224 </SMALL> Returns: <SMALL>225 </SMALL> A file object. <SMALL>226 </SMALL> """ <SMALL>227 </SMALL> return open(location + filename, mode) <SMALL>228 </SMALL> <SMALL>229 </SMALL> <SMALL>230 </SMALL>class GruyereRequestHandler(BaseHTTPRequestHandler): <SMALL>231 </SMALL> """Handle a http request.""" <SMALL>232 </SMALL> <SMALL>233 </SMALL> # An empty cookie <SMALL>234 </SMALL> NULL_COOKIE = {COOKIE_UID: None, COOKIE_ADMIN: False, COOKIE_AUTHOR: False} <SMALL>235 </SMALL> <SMALL>236 </SMALL> # Urls that can only be accessed by administrators. <SMALL>237 </SMALL> _PROTECTED_URLS = [ <SMALL>238 </SMALL> '/quit', <SMALL>239 </SMALL> '/reset' <SMALL>240 </SMALL> ] <SMALL>241 </SMALL> <SMALL>242 </SMALL> def _GetDatabase(self): <SMALL>243 </SMALL> """Gets the database.""" <SMALL>244 </SMALL> global stored_data <SMALL>245 </SMALL> if not stored_data: <SMALL>246 </SMALL> stored_data = data.DefaultData() <SMALL>247 </SMALL> return stored_data <SMALL>248 </SMALL> <SMALL>249 </SMALL> def _ResetDatabase(self): <SMALL>250 </SMALL> """Reset the database.""" <SMALL>251 </SMALL> # global stored_data <SMALL>252 </SMALL> stored_data = data.DefaultData() <SMALL>253 </SMALL> <SMALL>254 </SMALL> def _DoLogin(self, cookie, specials, params): <SMALL>255 </SMALL> """Handles the /login url: validates the user and creates a cookie. <SMALL>256 </SMALL> <SMALL>257 </SMALL> Args: <SMALL>258 </SMALL> cookie: The cookie for this request. <SMALL>259 </SMALL> specials: Other special values for this request. <SMALL>260 </SMALL> params: Cgi parameters. <SMALL>261 </SMALL> """ <SMALL>262 </SMALL> database = self._GetDatabase() <SMALL>263 </SMALL> message = '' <SMALL>264 </SMALL> if 'uid' in params and 'pw' in params: <SMALL>265 </SMALL> uid = self._GetParameter(params, 'uid') <SMALL>266 </SMALL> if uid in database: <SMALL>267 </SMALL> if database[uid]['pw'] == self._GetParameter(params, 'pw'): <SMALL>268 </SMALL> (cookie, new_cookie_text) = ( <SMALL>269 </SMALL> self._CreateCookie('GRUYERE', uid)) <SMALL>270 </SMALL> self._DoHome(cookie, specials, params, new_cookie_text) <SMALL>271 </SMALL> return <SMALL>272 </SMALL> message = 'Invalid user name or password.' <SMALL>273 </SMALL> # not logged in <SMALL>274 </SMALL> specials['_message'] = message <SMALL>275 </SMALL> self._SendTemplateResponse('/login.gtl', specials, params) <SMALL>276 </SMALL> <SMALL>277 </SMALL> def _DoLogout(self, cookie, specials, params): <SMALL>278 </SMALL> """Handles the /logout url: clears the cookie. <SMALL>279 </SMALL> <SMALL>280 </SMALL> Args: <SMALL>281 </SMALL> cookie: The cookie for this request. <SMALL>282 </SMALL> specials: Other special values for this request. <SMALL>283 </SMALL> params: Cgi parameters. <SMALL>284 </SMALL> """ <SMALL>285 </SMALL> (cookie, new_cookie_text) = ( <SMALL>286 </SMALL> self._CreateCookie('GRUYERE', None)) <SMALL>287 </SMALL> self._DoHome(cookie, specials, params, new_cookie_text) <SMALL>288 </SMALL> <SMALL>289 </SMALL> def _Do(self, cookie, specials, params): <SMALL>290 </SMALL> """Handles the home page (http://localhost/). <SMALL>291 </SMALL> <SMALL>292 </SMALL> Args: <SMALL>293 </SMALL> cookie: The cookie for this request. <SMALL>294 </SMALL> specials: Other special values for this request. <SMALL>295 </SMALL> params: Cgi parameters. <SMALL>296 </SMALL> """ <SMALL>297 </SMALL> self._DoHome(cookie, specials, params) <SMALL>298 </SMALL> <SMALL>299 </SMALL> def _DoHome(self, cookie, specials, params, new_cookie_text=None): <SMALL>300 </SMALL> """Renders the home page. <SMALL>301 </SMALL> <SMALL>302 </SMALL> Args: <SMALL>303 </SMALL> cookie: The cookie for this request. <SMALL>304 </SMALL> specials: Other special values for this request. <SMALL>305 </SMALL> params: Cgi parameters. <SMALL>306 </SMALL> new_cookie_text: New cookie. <SMALL>307 </SMALL> """ <SMALL>308 </SMALL> database = self._GetDatabase() <SMALL>309 </SMALL> specials[SPECIAL_COOKIE] = cookie <SMALL>310 </SMALL> if cookie and cookie.get(COOKIE_UID): <SMALL>311 </SMALL> specials[SPECIAL_PROFILE] = database.get(cookie[COOKIE_UID]) <SMALL>312 </SMALL> else: <SMALL>313 </SMALL> specials.pop(SPECIAL_PROFILE, None) <SMALL>314 </SMALL> self._SendTemplateResponse( <SMALL>315 </SMALL> '/home.gtl', specials, params, new_cookie_text) <SMALL>316 </SMALL> <SMALL>317 </SMALL> def _DoBadUrl(self, path, cookie, specials, params): <SMALL>318 </SMALL> """Handles invalid urls: displays an appropriate error message. <SMALL>319 </SMALL> <SMALL>320 </SMALL> Args: <SMALL>321 </SMALL> path: The invalid url. <SMALL>322 </SMALL> cookie: The cookie for this request. <SMALL>323 </SMALL> specials: Other special values for this request. <SMALL>324 </SMALL> params: Cgi parameters. <SMALL>325 </SMALL> """ <SMALL>326 </SMALL> self._SendError('Invalid request: %s' % (path,), cookie, specials, params) <SMALL>327 </SMALL> <SMALL>328 </SMALL> def _DoQuitserver(self, cookie, specials, params): <SMALL>329 </SMALL> """Handles the /quitserver url for administrators to quit the server. <SMALL>330 </SMALL> <SMALL>331 </SMALL> Args: <SMALL>332 </SMALL> cookie: The cookie for this request. (unused) <SMALL>333 </SMALL> specials: Other special values for this request. (unused) <SMALL>334 </SMALL> params: Cgi parameters. (unused) <SMALL>335 </SMALL> """ <SMALL>336 </SMALL> global quit_server <SMALL>337 </SMALL> quit_server = True <SMALL>338 </SMALL> self._SendTextResponse('Server quit.', None) <SMALL>339 </SMALL> <SMALL>340 </SMALL> def _AddParameter(self, name, params, data_dict, default=None): <SMALL>341 </SMALL> """Transfers a value (with a default) from the parameters to the data.""" <SMALL>342 </SMALL> if params.get(name): <SMALL>343 </SMALL> data_dict[name] = params[name][0] <SMALL>344 </SMALL> elif default is not None: <SMALL>345 </SMALL> data_dict[name] = default <SMALL>346 </SMALL> <SMALL>347 </SMALL> def _GetParameter(self, params, name, default=None): <SMALL>348 </SMALL> """Gets a parameter value with a default.""" <SMALL>349 </SMALL> if params.get(name): <SMALL>350 </SMALL> return params[name][0] <SMALL>351 </SMALL> return default <SMALL>352 </SMALL> <SMALL>353 </SMALL> def _GetSnippets(self, cookie, specials, create=False): <SMALL>354 </SMALL> """Returns all of the user's snippets.""" <SMALL>355 </SMALL> database = self._GetDatabase() <SMALL>356 </SMALL> try: <SMALL>357 </SMALL> profile = database[cookie[COOKIE_UID]] <SMALL>358 </SMALL> if create and 'snippets' not in profile: <SMALL>359 </SMALL> profile['snippets'] = [] <SMALL>360 </SMALL> snippets = profile['snippets'] <SMALL>361 </SMALL> except (KeyError, TypeError): <SMALL>362 </SMALL> _Log('Error getting snippets') <SMALL>363 </SMALL> return None <SMALL>364 </SMALL> return snippets <SMALL>365 </SMALL> <SMALL>366 </SMALL> def _DoNewsnippet2(self, cookie, specials, params): <SMALL>367 </SMALL> """Handles the /newsnippet2 url: actually add the snippet. <SMALL>368 </SMALL> <SMALL>369 </SMALL> Args: <SMALL>370 </SMALL> cookie: The cookie for this request. <SMALL>371 </SMALL> specials: Other special values for this request. <SMALL>372 </SMALL> params: Cgi parameters. <SMALL>373 </SMALL> """ <SMALL>374 </SMALL> snippet = self._GetParameter(params, 'snippet') <SMALL>375 </SMALL> if not snippet: <SMALL>376 </SMALL> self._SendError('No snippet!', cookie, specials, params) <SMALL>377 </SMALL> else: <SMALL>378 </SMALL> snippets = self._GetSnippets(cookie, specials, True) <SMALL>379 </SMALL> if snippets is not None: <SMALL>380 </SMALL> snippets.insert(0, snippet) <SMALL>381 </SMALL> self._SendRedirect('/snippets.gtl', specials[SPECIAL_UNIQUE_ID]) <SMALL>382 </SMALL> <SMALL>383 </SMALL> def _DoDeletesnippet(self, cookie, specials, params): <SMALL>384 </SMALL> """Handles the /deletesnippet url: delete the indexed snippet. <SMALL>385 </SMALL> <SMALL>386 </SMALL> Args: <SMALL>387 </SMALL> cookie: The cookie for this request. <SMALL>388 </SMALL> specials: Other special values for this request. <SMALL>389 </SMALL> params: Cgi parameters. <SMALL>390 </SMALL> """ <SMALL>391 </SMALL> index = self._GetParameter(params, 'index') <SMALL>392 </SMALL> snippets = self._GetSnippets(cookie, specials) <SMALL>393 </SMALL> try: <SMALL>394 </SMALL> del snippets[int(index)] <SMALL>395 </SMALL> except (IndexError, TypeError, ValueError): <SMALL>396 </SMALL> self._SendError( <SMALL>397 </SMALL> 'Invalid index (%s)' % (index,), <SMALL>398 </SMALL> cookie, specials, params) <SMALL>399 </SMALL> return <SMALL>400 </SMALL> self._SendRedirect('/snippets.gtl', specials[SPECIAL_UNIQUE_ID]) <SMALL>401 </SMALL> <SMALL>402 </SMALL> def _DoSaveprofile(self, cookie, specials, params): <SMALL>403 </SMALL> """Saves the user's profile. <SMALL>404 </SMALL> <SMALL>405 </SMALL> Args: <SMALL>406 </SMALL> cookie: The cookie for this request. <SMALL>407 </SMALL> specials: Other special values for this request. <SMALL>408 </SMALL> params: Cgi parameters. <SMALL>409 </SMALL> <SMALL>410 </SMALL> If the 'action' cgi parameter is 'new', then this is creating a new user <SMALL>411 </SMALL> and it's an error if the user already exists. If action is 'update', then <SMALL>412 </SMALL> this is editing an existing user's profile and it's an error if the user <SMALL>413 </SMALL> does not exist. <SMALL>414 </SMALL> """ <SMALL>415 </SMALL> <SMALL>416 </SMALL> # build new profile <SMALL>417 </SMALL> profile_data = {} <SMALL>418 </SMALL> uid = self._GetParameter(params, 'uid', cookie[COOKIE_UID]) <SMALL>419 </SMALL> newpw = self._GetParameter(params, 'pw') <SMALL>420 </SMALL> self._AddParameter('name', params, profile_data, uid) <SMALL>421 </SMALL> self._AddParameter('pw', params, profile_data) <SMALL>422 </SMALL> self._AddParameter('is_author', params, profile_data) <SMALL>423 </SMALL> self._AddParameter('is_admin', params, profile_data) <SMALL>424 </SMALL> self._AddParameter('private_snippet', params, profile_data) <SMALL>425 </SMALL> self._AddParameter('icon', params, profile_data) <SMALL>426 </SMALL> self._AddParameter('web_site', params, profile_data) <SMALL>427 </SMALL> self._AddParameter('color', params, profile_data) <SMALL>428 </SMALL> <SMALL>429 </SMALL> # Each case below has to set either error or redirect <SMALL>430 </SMALL> database = self._GetDatabase() <SMALL>431 </SMALL> message = None <SMALL>432 </SMALL> new_cookie_text = None <SMALL>433 </SMALL> action = self._GetParameter(params, 'action') <SMALL>434 </SMALL> if action == 'new': <SMALL>435 </SMALL> if uid in database: <SMALL>436 </SMALL> message = 'User already exists.' <SMALL>437 </SMALL> else: <SMALL>438 </SMALL> profile_data['pw'] = newpw <SMALL>439 </SMALL> database[uid] = profile_data <SMALL>440 </SMALL> (cookie, new_cookie_text) = self._CreateCookie('GRUYERE', uid) <SMALL>441 </SMALL> message = 'Account created.' # error message can also indicates success <SMALL>442 </SMALL> elif action == 'update': <SMALL>443 </SMALL> if uid not in database: <SMALL>444 </SMALL> message = 'User does not exist.' <SMALL>445 </SMALL> elif (newpw and database[uid]['pw'] != self._GetParameter(params, 'oldpw') <SMALL>446 </SMALL> and not cookie.get(COOKIE_ADMIN)): <SMALL>447 </SMALL> # must be admin or supply old pw to change password <SMALL>448 </SMALL> message = 'Incorrect password.' <SMALL>449 </SMALL> else: <SMALL>450 </SMALL> if newpw: <SMALL>451 </SMALL> profile_data['pw'] = newpw <SMALL>452 </SMALL> database[uid].update(profile_data) <SMALL>453 </SMALL> redirect = '/' <SMALL>454 </SMALL> else: <SMALL>455 </SMALL> message = 'Invalid request' <SMALL>456 </SMALL> _Log('SetProfile(%s, %s): %s' %(str(uid), str(action), str(message))) <SMALL>457 </SMALL> if message: <SMALL>458 </SMALL> self._SendError(message, cookie, specials, params, new_cookie_text) <SMALL>459 </SMALL> else: <SMALL>460 </SMALL> self._SendRedirect(redirect, specials[SPECIAL_UNIQUE_ID]) <SMALL>461 </SMALL> <SMALL>462 </SMALL> def _SendHtmlResponse(self, html, new_cookie_text=None): <SMALL>463 </SMALL> """Sends the provided html response with appropriate headers. <SMALL>464 </SMALL> <SMALL>465 </SMALL> Args: <SMALL>466 </SMALL> html: The response. <SMALL>467 </SMALL> new_cookie_text: New cookie to set. <SMALL>468 </SMALL> """ <SMALL>469 </SMALL> self.send_response(200) <SMALL>470 </SMALL> self.send_header('Content-type', 'text/html') <SMALL>471 </SMALL> self.send_header('Pragma', 'no-cache') <SMALL>472 </SMALL> if new_cookie_text: <SMALL>473 </SMALL> self.send_header('Set-Cookie', new_cookie_text) <SMALL>474 </SMALL> self.send_header('X-XSS-Protection', '0') <SMALL>475 </SMALL> self.end_headers() <SMALL>476 </SMALL> self.wfile.write(html) <SMALL>477 </SMALL> <SMALL>478 </SMALL> def _SendTextResponse(self, text, new_cookie_text=None): <SMALL>479 </SMALL> """Sends a verbatim text response.""" <SMALL>480 </SMALL> <SMALL>481 </SMALL> self._SendHtmlResponse('<pre>' + cgi.escape(text) + '</pre>', <SMALL>482 </SMALL> new_cookie_text) <SMALL>483 </SMALL> <SMALL>484 </SMALL> def _SendTemplateResponse(self, filename, specials, params, <SMALL>485 </SMALL> new_cookie_text=None): <SMALL>486 </SMALL> """Sends a response using a gtl template. <SMALL>487 </SMALL> <SMALL>488 </SMALL> Args: <SMALL>489 </SMALL> filename: The template file. <SMALL>490 </SMALL> specials: Other special values for this request. <SMALL>491 </SMALL> params: Cgi parameters. <SMALL>492 </SMALL> new_cookie_text: New cookie to set. <SMALL>493 </SMALL> """ <SMALL>494 </SMALL> f = None <SMALL>495 </SMALL> try: <SMALL>496 </SMALL> f = _Open(RESOURCE_PATH, filename) <SMALL>497 </SMALL> template = f.read() <SMALL>498 </SMALL> finally: <SMALL>499 </SMALL> if f: f.close() <SMALL>500 </SMALL> self._SendHtmlResponse( <SMALL>501 </SMALL> gtl.ExpandTemplate(template, specials, params), <SMALL>502 </SMALL> new_cookie_text) <SMALL>503 </SMALL> <SMALL>504 </SMALL> def _SendFileResponse(self, filename, cookie, specials, params): <SMALL>505 </SMALL> """Sends the contents of a file. <SMALL>506 </SMALL> <SMALL>507 </SMALL> Args: <SMALL>508 </SMALL> filename: The file to send. <SMALL>509 </SMALL> cookie: The cookie for this request. <SMALL>510 </SMALL> specials: Other special values for this request. <SMALL>511 </SMALL> params: Cgi parameters. <SMALL>512 </SMALL> """ <SMALL>513 </SMALL> content_type = None <SMALL>514 </SMALL> if filename.endswith('.gtl'): <SMALL>515 </SMALL> self._SendTemplateResponse(filename, specials, params) <SMALL>516 </SMALL> return <SMALL>517 </SMALL> <SMALL>518 </SMALL> name_only = filename[filename.rfind('/'):] <SMALL>519 </SMALL> extension = name_only[name_only.rfind('.'):] <SMALL>520 </SMALL> if '.' not in extension: <SMALL>521 </SMALL> content_type = 'text/plain' <SMALL>522 </SMALL> elif extension in RESOURCE_CONTENT_TYPES: <SMALL>523 </SMALL> content_type = RESOURCE_CONTENT_TYPES[extension] <SMALL>524 </SMALL> else: <SMALL>525 </SMALL> self._SendError( <SMALL>526 </SMALL> 'Unrecognized file type (%s).' % (filename,), <SMALL>527 </SMALL> cookie, specials, params) <SMALL>528 </SMALL> return <SMALL>529 </SMALL> f = None <SMALL>530 </SMALL> try: <SMALL>531 </SMALL> f = _Open(RESOURCE_PATH, filename, 'rb') <SMALL>532 </SMALL> self.send_response(200) <SMALL>533 </SMALL> self.send_header('Content-type', content_type) <SMALL>534 </SMALL> # Always cache static resources <SMALL>535 </SMALL> self.send_header('Cache-control', 'public, max-age=7200') <SMALL>536 </SMALL> self.send_header('X-XSS-Protection', '0') <SMALL>537 </SMALL> self.end_headers() <SMALL>538 </SMALL> self.wfile.write(f.read()) <SMALL>539 </SMALL> finally: <SMALL>540 </SMALL> if f: f.close() <SMALL>541 </SMALL> <SMALL>542 </SMALL> def _SendError(self, message, cookie, specials, params, new_cookie_text=None): <SMALL>543 </SMALL> """Sends an error message (using the error.gtl template). <SMALL>544 </SMALL> <SMALL>545 </SMALL> Args: <SMALL>546 </SMALL> message: The error to display. <SMALL>547 </SMALL> cookie: The cookie for this request. (unused) <SMALL>548 </SMALL> specials: Other special values for this request. <SMALL>549 </SMALL> params: Cgi parameters. <SMALL>550 </SMALL> new_cookie_text: New cookie to set. <SMALL>551 </SMALL> """ <SMALL>552 </SMALL> specials['_message'] = message <SMALL>553 </SMALL> self._SendTemplateResponse( <SMALL>554 </SMALL> '/error.gtl', specials, params, new_cookie_text) <SMALL>555 </SMALL> <SMALL>556 </SMALL> def _CreateCookie(self, cookie_name, uid): <SMALL>557 </SMALL> """Creates a cookie for this user. <SMALL>558 </SMALL> <SMALL>559 </SMALL> Args: <SMALL>560 </SMALL> cookie_name: Cookie to create. <SMALL>561 </SMALL> uid: The user. <SMALL>562 </SMALL> <SMALL>563 </SMALL> Returns: <SMALL>564 </SMALL> (cookie, new_cookie_text). <SMALL>565 </SMALL> <SMALL>566 </SMALL> The cookie contains all the information we need to know about <SMALL>567 </SMALL> the user for normal operations, including whether or not the user <SMALL>568 </SMALL> should have access to the authoring pages or the admin pages. <SMALL>569 </SMALL> The cookie is signed with a hash function. <SMALL>570 </SMALL> """ <SMALL>571 </SMALL> if uid is None: <SMALL>572 </SMALL> return (self.NULL_COOKIE, cookie_name + '=; path=/') <SMALL>573 </SMALL> database = self._GetDatabase() <SMALL>574 </SMALL> profile = database[uid] <SMALL>575 </SMALL> if profile.get('is_author', False): <SMALL>576 </SMALL> is_author = 'author' <SMALL>577 </SMALL> else: <SMALL>578 </SMALL> is_author = '' <SMALL>579 </SMALL> if profile.get('is_admin', False): <SMALL>580 </SMALL> is_admin = 'admin' <SMALL>581 </SMALL> else: <SMALL>582 </SMALL> is_admin = '' <SMALL>583 </SMALL> <SMALL>584 </SMALL> c = {COOKIE_UID: uid, COOKIE_ADMIN: is_admin, COOKIE_AUTHOR: is_author} <SMALL>585 </SMALL> c_data = '%s|%s|%s' % (uid, is_admin, is_author) <SMALL>586 </SMALL> <SMALL>587 </SMALL> # global cookie_secret; only use positive hash values <SMALL>588 </SMALL> h_data = str(hash(cookie_secret + c_data) & 0x7FFFFFF) <SMALL>589 </SMALL> c_text = '%s=%s|%s; path=/' % (cookie_name, h_data, c_data) <SMALL>590 </SMALL> return (c, c_text) <SMALL>591 </SMALL> <SMALL>592 </SMALL> def _GetCookie(self, cookie_name): <SMALL>593 </SMALL> """Reads, verifies and parses the cookie. <SMALL>594 </SMALL> <SMALL>595 </SMALL> Args: <SMALL>596 </SMALL> cookie_name: The cookie to get. <SMALL>597 </SMALL> <SMALL>598 </SMALL> Returns: <SMALL>599 </SMALL> a dict containing user, is_admin, and is_author if the cookie <SMALL>600 </SMALL> is present and valid. Otherwise, None. <SMALL>601 </SMALL> """ <SMALL>602 </SMALL> cookies = self.headers.get('Cookie') <SMALL>603 </SMALL> if isinstance(cookies, str): <SMALL>604 </SMALL> for c in cookies.split(';'): <SMALL>605 </SMALL> matched_cookie = self._MatchCookie(cookie_name, c) <SMALL>606 </SMALL> if matched_cookie: <SMALL>607 </SMALL> return self._ParseCookie(matched_cookie) <SMALL>608 </SMALL> return self.NULL_COOKIE <SMALL>609 </SMALL> <SMALL>610 </SMALL> def _MatchCookie(self, cookie_name, cookie): <SMALL>611 </SMALL> """Matches the cookie. <SMALL>612 </SMALL> <SMALL>613 </SMALL> Args: <SMALL>614 </SMALL> cookie_name: The name of the cookie. <SMALL>615 </SMALL> cookie: The full cookie (name=value). <SMALL>616 </SMALL> <SMALL>617 </SMALL> Returns: <SMALL>618 </SMALL> The cookie if it matches or None if it doesn't match. <SMALL>619 </SMALL> """ <SMALL>620 </SMALL> try: <SMALL>621 </SMALL> (cn, cd) = cookie.strip().split('=', 1) <SMALL>622 </SMALL> if cn != cookie_name: <SMALL>623 </SMALL> return None <SMALL>624 </SMALL> except (IndexError, ValueError): <SMALL>625 </SMALL> return None <SMALL>626 </SMALL> return cd <SMALL>627 </SMALL> <SMALL>628 </SMALL> def _ParseCookie(self, cookie): <SMALL>629 </SMALL> """Parses the cookie and returns NULL_COOKIE if it's invalid. <SMALL>630 </SMALL> <SMALL>631 </SMALL> Args: <SMALL>632 </SMALL> cookie: The text of the cookie. <SMALL>633 </SMALL> <SMALL>634 </SMALL> Returns: <SMALL>635 </SMALL> A map containing the values in the cookie. <SMALL>636 </SMALL> """ <SMALL>637 </SMALL> try: <SMALL>638 </SMALL> (hashed, cookie_data) = cookie.split('|', 1) <SMALL>639 </SMALL> # global cookie_secret <SMALL>640 </SMALL> if hashed != str(hash(cookie_secret + cookie_data) & 0x7FFFFFF): <SMALL>641 </SMALL> return self.NULL_COOKIE <SMALL>642 </SMALL> values = cookie_data.split('|') <SMALL>643 </SMALL> return { <SMALL>644 </SMALL> COOKIE_UID: values[0], <SMALL>645 </SMALL> COOKIE_ADMIN: values[1] == 'admin', <SMALL>646 </SMALL> COOKIE_AUTHOR: values[2] == 'author', <SMALL>647 </SMALL> } <SMALL>648 </SMALL> except (IndexError, ValueError): <SMALL>649 </SMALL> return self.NULL_COOKIE <SMALL>650 </SMALL> <SMALL>651 </SMALL> def _DoReset(self, cookie, specials, params): # debug only; resets this db <SMALL>652 </SMALL> """Handles the /reset url for administrators to reset the database. <SMALL>653 </SMALL> <SMALL>654 </SMALL> Args: <SMALL>655 </SMALL> cookie: The cookie for this request. (unused) <SMALL>656 </SMALL> specials: Other special values for this request. (unused) <SMALL>657 </SMALL> params: Cgi parameters. (unused) <SMALL>658 </SMALL> """ <SMALL>659 </SMALL> self._ResetDatabase() <SMALL>660 </SMALL> self._SendTextResponse('Server reset to default values...', None) <SMALL>661 </SMALL> <SMALL>662 </SMALL> def _DoUpload2(self, cookie, specials, params): <SMALL>663 </SMALL> """Handles the /upload2 url: finish the upload and save the file. <SMALL>664 </SMALL> <SMALL>665 </SMALL> Args: <SMALL>666 </SMALL> cookie: The cookie for this request. <SMALL>667 </SMALL> specials: Other special values for this request. <SMALL>668 </SMALL> params: Cgi parameters. (unused) <SMALL>669 </SMALL> """ <SMALL>670 </SMALL> (filename, file_data) = self._ExtractFileFromRequest() <SMALL>671 </SMALL> directory = self._MakeUserDirectory(cookie[COOKIE_UID]) <SMALL>672 </SMALL> <SMALL>673 </SMALL> message = None <SMALL>674 </SMALL> url = None <SMALL>675 </SMALL> try: <SMALL>676 </SMALL> f = _Open(directory, filename, 'wb') <SMALL>677 </SMALL> f.write(file_data) <SMALL>678 </SMALL> f.close() <SMALL>679 </SMALL> (host, port) = http_server.server_address <SMALL>680 </SMALL> url = 'http://%s:%d/%s/%s/%s' % ( <SMALL>681 </SMALL> host, port, specials[SPECIAL_UNIQUE_ID], cookie[COOKIE_UID], filename) <SMALL>682 </SMALL> except IOError, ex: <SMALL>683 </SMALL> message = 'Couldn\'t write file %s: %s' % (filename, ex.message) <SMALL>684 </SMALL> _Log(message) <SMALL>685 </SMALL> <SMALL>686 </SMALL> specials['_message'] = message <SMALL>687 </SMALL> self._SendTemplateResponse( <SMALL>688 </SMALL> '/upload2.gtl', specials, <SMALL>689 </SMALL> {'url': url}) <SMALL>690 </SMALL> <SMALL>691 </SMALL> def _ExtractFileFromRequest(self): <SMALL>692 </SMALL> """Extracts the file from an upload request. <SMALL>693 </SMALL> <SMALL>694 </SMALL> Returns: <SMALL>695 </SMALL> (filename, file_data) <SMALL>696 </SMALL> """ <SMALL>697 </SMALL> form = cgi.FieldStorage( <SMALL>698 </SMALL> fp=self.rfile, <SMALL>699 </SMALL> headers=self.headers, <SMALL>700 </SMALL> environ={'REQUEST_METHOD': 'POST', <SMALL>701 </SMALL> 'CONTENT_TYPE': self.headers.getheader('content-type')}) <SMALL>702 </SMALL> <SMALL>703 </SMALL> upload_file = form['upload_file'] <SMALL>704 </SMALL> file_data = upload_file.file.read() <SMALL>705 </SMALL> return (upload_file.filename, file_data) <SMALL>706 </SMALL> <SMALL>707 </SMALL> def _MakeUserDirectory(self, uid): <SMALL>708 </SMALL> """Creates a separate directory for each user to avoid upload conflicts. <SMALL>709 </SMALL> <SMALL>710 </SMALL> Args: <SMALL>711 </SMALL> uid: The user to create a directory for. <SMALL>712 </SMALL> <SMALL>713 </SMALL> Returns: <SMALL>714 </SMALL> The new directory path (/uid/). <SMALL>715 </SMALL> """ <SMALL>716 </SMALL> <SMALL>717 </SMALL> directory = RESOURCE_PATH + os.sep + str(uid) + os.sep <SMALL>718 </SMALL> try: <SMALL>719 </SMALL> print 'mkdir: ', directory <SMALL>720 </SMALL> os.mkdir(directory) <SMALL>721 </SMALL> # throws an exception if directory already exists, <SMALL>722 </SMALL> # however exception type varies by platform <SMALL>723 </SMALL> except Exception: <SMALL>724 </SMALL> pass # just ignore it if it already exists <SMALL>725 </SMALL> return directory <SMALL>726 </SMALL> <SMALL>727 </SMALL> def _SendRedirect(self, url, unique_id): <SMALL>728 </SMALL> """Sends a 302 redirect. <SMALL>729 </SMALL> <SMALL>730 </SMALL> Automatically adds the unique_id. <SMALL>731 </SMALL> <SMALL>732 </SMALL> Args: <SMALL>733 </SMALL> url: The location to redirect to which must start with '/'. <SMALL>734 </SMALL> unique_id: The unique id to include in the url. <SMALL>735 </SMALL> """ <SMALL>736 </SMALL> if not url: <SMALL>737 </SMALL> url = '/' <SMALL>738 </SMALL> url = '/' + unique_id + url <SMALL>739 </SMALL> self.send_response(302) <SMALL>740 </SMALL> self.send_header('Location', url) <SMALL>741 </SMALL> self.send_header('Pragma', 'no-cache') <SMALL>742 </SMALL> self.send_header('Content-type', 'text/html') <SMALL>743 </SMALL> self.send_header('X-XSS-Protection', '0') <SMALL>744 </SMALL> self.end_headers() <SMALL>745 </SMALL> self.wfile.write( <SMALL>746 </SMALL> '''<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML//EN'> <SMALL>747 </SMALL> <html><body> <SMALL>748 </SMALL> <title>302 Redirect</title> <SMALL>749 </SMALL> Redirected <a href="%s">here</a> <SMALL>750 </SMALL> </body></html>''' <SMALL>751 </SMALL> % (url,)) <SMALL>752 </SMALL> <SMALL>753 </SMALL> def _GetHandlerFunction(self, path): <SMALL>754 </SMALL> try: <SMALL>755 </SMALL> return getattr(GruyereRequestHandler, '_Do' + path[1:].capitalize()) <SMALL>756 </SMALL> except AttributeError: <SMALL>757 </SMALL> return None <SMALL>758 </SMALL> <SMALL>759 </SMALL> def do_POST(self): # part of BaseHTTPRequestHandler interface <SMALL>760 </SMALL> self.DoGetOrPost() <SMALL>761 </SMALL> <SMALL>762 </SMALL> def do_GET(self): # part of BaseHTTPRequestHandler interface <SMALL>763 </SMALL> self.DoGetOrPost() <SMALL>764 </SMALL> <SMALL>765 </SMALL> def DoGetOrPost(self): <SMALL>766 </SMALL> """Validate an http get or post request and call HandleRequest.""" <SMALL>767 </SMALL> <SMALL>768 </SMALL> url = urlparse(self.path) <SMALL>769 </SMALL> path = url[2] <SMALL>770 </SMALL> query = url[4] <SMALL>771 </SMALL> <SMALL>772 </SMALL> # Normally, Gruyere only accepts connections to/from localhost. If you <SMALL>773 </SMALL> # would like to allow access from other ip addresses, add the addresses <SMALL>774 </SMALL> # of the other machines to allowed_ips and change insecure_mode to True <SMALL>775 </SMALL> # above. This makes the application more vulnerable to a real attack so <SMALL>776 </SMALL> # you should only add ips of machines you completely control and make <SMALL>777 </SMALL> # sure that you are not using them to access any other web pages while <SMALL>778 </SMALL> # you are using Gruyere. <SMALL>779 </SMALL> <SMALL>780 </SMALL> allowed_ips = ['127.0.0.1'] <SMALL>781 </SMALL> <SMALL>782 </SMALL> # WARNING! DO NOT CHANGE THE FOLLOWING SECTION OF CODE! <SMALL>783 </SMALL> <SMALL>784 </SMALL> # This application is very exploitable. See main for details. What we're <SMALL>785 </SMALL> # doing here is (2) and (3) on the previous list: <SMALL>786 </SMALL> # (2) If a request is received from any IP other than localhost, quit. <SMALL>787 </SMALL> # An external attacker could still mount an attack on this IP by putting <SMALL>788 </SMALL> # an attack on an external web page, e.g., a web page that redirects to <SMALL>789 </SMALL> # a vulnerable url on 127.0.0.1 (which is why we use a random number). <SMALL>790 </SMALL> # (3) Inject a random identifier as the first part of the path and <SMALL>791 </SMALL> # quit if a request is received without this identifier (except for an <SMALL>792 </SMALL> # empty path which redirects and /favicon.ico). <SMALL>793 </SMALL> <SMALL>794 </SMALL> request_ip = self.client_address[0] # DO NOT CHANGE <SMALL>795 </SMALL> if request_ip not in allowed_ips: # DO NOT CHANGE <SMALL>796 </SMALL> print >>sys.stderr, ( # DO NOT CHANGE <SMALL>797 </SMALL> 'DANGER! Request from bad ip: ' + request_ip) # DO NOT CHANGE <SMALL>798 </SMALL> _Exit('bad_ip') # DO NOT CHANGE <SMALL>799 </SMALL> <SMALL>800 </SMALL> if (server_unique_id not in path # DO NOT CHANGE <SMALL>801 </SMALL> and path != '/favicon.ico'): # DO NOT CHANGE <SMALL>802 </SMALL> if path == '' or path == '/': # DO NOT CHANGE <SMALL>803 </SMALL> self._SendRedirect('/', server_unique_id) # DO NOT CHANGE <SMALL>804 </SMALL> return # DO NOT CHANGE <SMALL>805 </SMALL> else: # DO NOT CHANGE <SMALL>806 </SMALL> print >>sys.stderr, ( # DO NOT CHANGE <SMALL>807 </SMALL> 'DANGER! Request without unique id: ' + path) # DO NOT CHANGE <SMALL>808 </SMALL> _Exit('bad_id') # DO NOT CHANGE <SMALL>809 </SMALL> <SMALL>810 </SMALL> path = path.replace('/' + server_unique_id, '', 1) # DO NOT CHANGE <SMALL>811 </SMALL> <SMALL>812 </SMALL> # END WARNING! <SMALL>813 </SMALL> <SMALL>814 </SMALL> self.HandleRequest(path, query, server_unique_id) <SMALL>815 </SMALL> <SMALL>816 </SMALL> def HandleRequest(self, path, query, unique_id): <SMALL>817 </SMALL> """Handles an http request. <SMALL>818 </SMALL> <SMALL>819 </SMALL> Args: <SMALL>820 </SMALL> path: The path part of the url, with leading slash. <SMALL>821 </SMALL> query: The query part of the url, without leading question mark. <SMALL>822 </SMALL> unique_id: The unique id from the url. <SMALL>823 </SMALL> """ <SMALL>824 </SMALL> <SMALL>825 </SMALL> path = urllib.unquote(path) <SMALL>826 </SMALL> <SMALL>827 </SMALL> if not path: <SMALL>828 </SMALL> self._SendRedirect('/', server_unique_id) <SMALL>829 </SMALL> return <SMALL>830 </SMALL> params = cgi.parse_qs(query) # url.query <SMALL>831 </SMALL> specials = {} <SMALL>832 </SMALL> cookie = self._GetCookie('GRUYERE') <SMALL>833 </SMALL> database = self._GetDatabase() <SMALL>834 </SMALL> specials[SPECIAL_COOKIE] = cookie <SMALL>835 </SMALL> specials[SPECIAL_DB] = database <SMALL>836 </SMALL> specials[SPECIAL_PROFILE] = database.get(cookie.get(COOKIE_UID)) <SMALL>837 </SMALL> specials[SPECIAL_PARAMS] = params <SMALL>838 </SMALL> specials[SPECIAL_UNIQUE_ID] = unique_id <SMALL>839 </SMALL> <SMALL>840 </SMALL> if path in self._PROTECTED_URLS and not cookie[COOKIE_ADMIN]: <SMALL>841 </SMALL> self._SendError('Invalid request', cookie, specials, params) <SMALL>842 </SMALL> return <SMALL>843 </SMALL> <SMALL>844 </SMALL> try: <SMALL>845 </SMALL> handler = self._GetHandlerFunction(path) <SMALL>846 </SMALL> if callable(handler): <SMALL>847 </SMALL> (handler)(self, cookie, specials, params) <SMALL>848 </SMALL> else: <SMALL>849 </SMALL> try: <SMALL>850 </SMALL> self._SendFileResponse(path, cookie, specials, params) <SMALL>851 </SMALL> except IOError: <SMALL>852 </SMALL> self._DoBadUrl(path, cookie, specials, params) <SMALL>853 </SMALL> except KeyboardInterrupt: <SMALL>854 </SMALL> _Exit('KeyboardInterrupt') <SMALL>855 </SMALL> <SMALL>856 </SMALL> <SMALL>857 </SMALL>def _Log(message): <SMALL>858 </SMALL> print >>sys.stderr, message <SMALL>859 </SMALL> <SMALL>860 </SMALL> <SMALL>861 </SMALL>if __name__ == '__main__': <SMALL>862 </SMALL> main() </PRE></BODY></HTML> |
| URL | http://google-gruyere.appspot.com/code/gtl.py |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 381 bytes. |
GET http://google-gruyere.appspot.com/code/gtl.py HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/code/?gtl.py Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 244 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: 7c3910c9ad359e9786c9ea96d012a53d Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 15231 |
| Response Body - size: 15,231 bytes. |
<HTML>
<STYLE> SMALL { color: #AAAAFF; font-size: 75% } BIG { color: #3333FF; font-size: 150%; font-weight: bold} </STYLE><BODY><PRE> <BIG>gtl.py</BIG> <SMALL> 1 </SMALL>"""Gruyere Template Language, part of Gruyere, a web application with holes. <SMALL> 2 </SMALL> <SMALL> 3 </SMALL>Copyright 2017 Google Inc. All rights reserved. <SMALL> 4 </SMALL> <SMALL> 5 </SMALL>This code is licensed under the https://creativecommons.org/licenses/by-nd/3.0/us/ <SMALL> 6 </SMALL>Creative Commons Attribution-No Derivative Works 3.0 United States license. <SMALL> 7 </SMALL> <SMALL> 8 </SMALL>DO NOT COPY THIS CODE! <SMALL> 9 </SMALL> <SMALL> 10 </SMALL>This application is a small self-contained web application with numerous <SMALL> 11 </SMALL>security holes. It is provided for use with the Web Application Exploits and <SMALL> 12 </SMALL>Defenses codelab. You may modify the code for your own use while doing the <SMALL> 13 </SMALL>codelab but you may not distribute the modified code. Brief excerpts of this <SMALL> 14 </SMALL>code may be used for educational or instructional purposes provided this <SMALL> 15 </SMALL>notice is kept intact. By using Gruyere you agree to the Terms of Service <SMALL> 16 </SMALL>https://www.google.com/intl/en/policies/terms/ <SMALL> 17 </SMALL>""" <SMALL> 18 </SMALL> <SMALL> 19 </SMALL>__author__ = 'Bruce Leban' <SMALL> 20 </SMALL> <SMALL> 21 </SMALL># system modules <SMALL> 22 </SMALL>import cgi <SMALL> 23 </SMALL>import logging <SMALL> 24 </SMALL>import operator <SMALL> 25 </SMALL>import os <SMALL> 26 </SMALL>import pprint <SMALL> 27 </SMALL>import sys <SMALL> 28 </SMALL> <SMALL> 29 </SMALL># our modules <SMALL> 30 </SMALL>import gruyere <SMALL> 31 </SMALL>import sanitize <SMALL> 32 </SMALL> <SMALL> 33 </SMALL> <SMALL> 34 </SMALL>def ExpandTemplate(template, specials, params, name=''): <SMALL> 35 </SMALL> """Expands a template. <SMALL> 36 </SMALL> <SMALL> 37 </SMALL> Args: <SMALL> 38 </SMALL> template: a string template. <SMALL> 39 </SMALL> specials: a dict of special values. <SMALL> 40 </SMALL> params: a dict of parameter values. <SMALL> 41 </SMALL> name: the name of the _this object. <SMALL> 42 </SMALL> <SMALL> 43 </SMALL> Returns: <SMALL> 44 </SMALL> the expanded template. <SMALL> 45 </SMALL> <SMALL> 46 </SMALL> The template language includes these block structures: <SMALL> 47 </SMALL> <SMALL> 48 </SMALL> [[include:<filename>]] ...[[/include:<filename>]] <SMALL> 49 </SMALL> Insert the file or if the file cannot be opened insert the contents of <SMALL> 50 </SMALL> the block. The path should use / as a separator regardless of what <SMALL> 51 </SMALL> the underlying operating system is. <SMALL> 52 </SMALL> <SMALL> 53 </SMALL> [[for:<variable>]] ... [[/for:<variable>]] <SMALL> 54 </SMALL> Iterate over the variable (which should be a mapping or sequence) and <SMALL> 55 </SMALL> insert the block once for each value. Inside the loop _key is bound to <SMALL> 56 </SMALL> the key value for the iteration. <SMALL> 57 </SMALL> <SMALL> 58 </SMALL> [[if:<variable>]] ... [[/if:<variable>]] <SMALL> 59 </SMALL> Expand the contents of the block if the variable is not 'false'. There <SMALL> 60 </SMALL> is no else; use [[if:!<variable>]] instead. <SMALL> 61 </SMALL> <SMALL> 62 </SMALL> Note that in each case the end tags must match the begin tags with a <SMALL> 63 </SMALL> leading slash. This prevents mismatched tags and makes it easier to parse. <SMALL> 64 </SMALL> <SMALL> 65 </SMALL> The variable syntax is: <SMALL> 66 </SMALL> <SMALL> 67 </SMALL> {{<field>[.<field>]*[:<escaper>]}} <SMALL> 68 </SMALL> <SMALL> 69 </SMALL> where <field> is: <SMALL> 70 </SMALL> <SMALL> 71 </SMALL> a key to extract from a mapping <SMALL> 72 </SMALL> a number to extract from a sequence <SMALL> 73 </SMALL> <SMALL> 74 </SMALL> Variable names that start with '_' are special values: <SMALL> 75 </SMALL> _key = iteration key (inside loops) <SMALL> 76 </SMALL> _this = iteration value (inside loop) <SMALL> 77 </SMALL> _db = the database <SMALL> 78 </SMALL> _cookie = the user's cookie <SMALL> 79 </SMALL> _profile = the user's profile ~ _db.*(_cookie.user) <SMALL> 80 </SMALL> <SMALL> 81 </SMALL> If a field name starts with '*' it refers to a dereferenced parameter (orx <SMALL> 82 </SMALL> *_this). For example, _db.*uid retrieves the entry from _db matching the <SMALL> 83 </SMALL> uid parameter. <SMALL> 84 </SMALL> <SMALL> 85 </SMALL> The comment syntax is: <SMALL> 86 </SMALL> <SMALL> 87 </SMALL> {{#<comment>}} <SMALL> 88 </SMALL> """ <SMALL> 89 </SMALL> t = _ExpandBlocks(template, specials, params, name) <SMALL> 90 </SMALL> t = _ExpandVariables(t, specials, params, name) <SMALL> 91 </SMALL> return t <SMALL> 92 </SMALL> <SMALL> 93 </SMALL> <SMALL> 94 </SMALL>BLOCK_OPEN = '[[' <SMALL> 95 </SMALL>END_BLOCK_OPEN = '[[/' <SMALL> 96 </SMALL>BLOCK_CLOSE = ']]' <SMALL> 97 </SMALL> <SMALL> 98 </SMALL> <SMALL> 99 </SMALL>def _ExpandBlocks(template, specials, params, name): <SMALL>100 </SMALL> """Expands all the blocks in a template.""" <SMALL>101 </SMALL> result = [] <SMALL>102 </SMALL> rest = template <SMALL>103 </SMALL> while rest: <SMALL>104 </SMALL> tag, before_tag, after_tag = _FindTag(rest, BLOCK_OPEN, BLOCK_CLOSE) <SMALL>105 </SMALL> if tag is None: <SMALL>106 </SMALL> break <SMALL>107 </SMALL> end_tag = END_BLOCK_OPEN + tag + BLOCK_CLOSE <SMALL>108 </SMALL> before_end = rest.find(end_tag, after_tag) <SMALL>109 </SMALL> if before_end < 0: <SMALL>110 </SMALL> break <SMALL>111 </SMALL> after_end = before_end + len(end_tag) <SMALL>112 </SMALL> <SMALL>113 </SMALL> result.append(rest[:before_tag]) <SMALL>114 </SMALL> block = rest[after_tag:before_end] <SMALL>115 </SMALL> result.append(_ExpandBlock(tag, block, specials, params, name)) <SMALL>116 </SMALL> rest = rest[after_end:] <SMALL>117 </SMALL> return ''.join(result) + rest <SMALL>118 </SMALL> <SMALL>119 </SMALL> <SMALL>120 </SMALL>VAR_OPEN = '{{' <SMALL>121 </SMALL>VAR_CLOSE = '}}' <SMALL>122 </SMALL> <SMALL>123 </SMALL> <SMALL>124 </SMALL>def _ExpandVariables(template, specials, params, name): <SMALL>125 </SMALL> """Expands all the variables in a template.""" <SMALL>126 </SMALL> result = [] <SMALL>127 </SMALL> rest = template <SMALL>128 </SMALL> while rest: <SMALL>129 </SMALL> tag, before_tag, after_tag = _FindTag(rest, VAR_OPEN, VAR_CLOSE) <SMALL>130 </SMALL> if tag is None: <SMALL>131 </SMALL> break <SMALL>132 </SMALL> result.append(rest[:before_tag]) <SMALL>133 </SMALL> result.append(str(_ExpandVariable(tag, specials, params, name))) <SMALL>134 </SMALL> rest = rest[after_tag:] <SMALL>135 </SMALL> return ''.join(result) + rest <SMALL>136 </SMALL> <SMALL>137 </SMALL> <SMALL>138 </SMALL>FOR_TAG = 'for' <SMALL>139 </SMALL>IF_TAG = 'if' <SMALL>140 </SMALL>INCLUDE_TAG = 'include' <SMALL>141 </SMALL> <SMALL>142 </SMALL> <SMALL>143 </SMALL>def _ExpandBlock(tag, template, specials, params, name): <SMALL>144 </SMALL> """Expands a single template block.""" <SMALL>145 </SMALL> <SMALL>146 </SMALL> tag_type, block_var = tag.split(':', 1) <SMALL>147 </SMALL> if tag_type == INCLUDE_TAG: <SMALL>148 </SMALL> return _ExpandInclude(tag, block_var, template, specials, params, name) <SMALL>149 </SMALL> elif tag_type == IF_TAG: <SMALL>150 </SMALL> block_data = _ExpandVariable(block_var, specials, params, name) <SMALL>151 </SMALL> if block_data: <SMALL>152 </SMALL> return ExpandTemplate(template, specials, params, name) <SMALL>153 </SMALL> return '' <SMALL>154 </SMALL> elif tag_type == FOR_TAG: <SMALL>155 </SMALL> block_data = _ExpandVariable(block_var, specials, params, name) <SMALL>156 </SMALL> return _ExpandFor(tag, template, specials, block_data) <SMALL>157 </SMALL> else: <SMALL>158 </SMALL> _Log('Error: Invalid block: %s' % (tag,)) <SMALL>159 </SMALL> return '' <SMALL>160 </SMALL> <SMALL>161 </SMALL> <SMALL>162 </SMALL>def _ExpandInclude(_, filename, template, specials, params, name): <SMALL>163 </SMALL> """Expands an include block (or insert the template on an error).""" <SMALL>164 </SMALL> result = '' <SMALL>165 </SMALL> # replace /s with local file system equivalent <SMALL>166 </SMALL> fname = os.sep + filename.replace('/', os.sep) <SMALL>167 </SMALL> f = None <SMALL>168 </SMALL> try: <SMALL>169 </SMALL> try: <SMALL>170 </SMALL> f = gruyere._Open(gruyere.RESOURCE_PATH, fname) <SMALL>171 </SMALL> result = f.read() <SMALL>172 </SMALL> except IOError: <SMALL>173 </SMALL> _Log('Error: missing filename: %s' % (filename,)) <SMALL>174 </SMALL> result = template <SMALL>175 </SMALL> finally: <SMALL>176 </SMALL> if f: f.close() <SMALL>177 </SMALL> return ExpandTemplate(result, specials, params, name) <SMALL>178 </SMALL> <SMALL>179 </SMALL> <SMALL>180 </SMALL>def _ExpandFor(tag, template, specials, block_data): <SMALL>181 </SMALL> """Expands a for block iterating over the block_data.""" <SMALL>182 </SMALL> result = [] <SMALL>183 </SMALL> if operator.isMappingType(block_data): <SMALL>184 </SMALL> for v in block_data: <SMALL>185 </SMALL> result.append(ExpandTemplate(template, specials, block_data[v], v)) <SMALL>186 </SMALL> elif operator.isSequenceType(block_data): <SMALL>187 </SMALL> for i in xrange(len(block_data)): <SMALL>188 </SMALL> result.append(ExpandTemplate(template, specials, block_data[i], str(i))) <SMALL>189 </SMALL> else: <SMALL>190 </SMALL> _Log('Error: Invalid type: %s' % (tag,)) <SMALL>191 </SMALL> return '' <SMALL>192 </SMALL> return ''.join(result) <SMALL>193 </SMALL> <SMALL>194 </SMALL> <SMALL>195 </SMALL>def _ExpandVariable(var, specials, params, name, default=''): <SMALL>196 </SMALL> """Gets a variable value.""" <SMALL>197 </SMALL> if var.startswith('#'): # this is a comment. <SMALL>198 </SMALL> return '' <SMALL>199 </SMALL> <SMALL>200 </SMALL> # Strip out leading ! which negates value <SMALL>201 </SMALL> inverted = var.startswith('!') <SMALL>202 </SMALL> if inverted: <SMALL>203 </SMALL> var = var[1:] <SMALL>204 </SMALL> <SMALL>205 </SMALL> # Strip out trailing :<escaper> <SMALL>206 </SMALL> escaper_name = None <SMALL>207 </SMALL> if var.find(':') >= 0: <SMALL>208 </SMALL> (var, escaper_name) = var.split(':', 1) <SMALL>209 </SMALL> <SMALL>210 </SMALL> value = _ExpandValue(var, specials, params, name, default) <SMALL>211 </SMALL> if inverted: <SMALL>212 </SMALL> value = not value <SMALL>213 </SMALL> <SMALL>214 </SMALL> if escaper_name == 'text': <SMALL>215 </SMALL> value = cgi.escape(str(value)) <SMALL>216 </SMALL> elif escaper_name == 'html': <SMALL>217 </SMALL> value = sanitize.SanitizeHtml(str(value)) <SMALL>218 </SMALL> elif escaper_name == 'pprint': # for debugging <SMALL>219 </SMALL> value = '<pre>' + cgi.escape(pprint.pformat(value)) + '</pre>' <SMALL>220 </SMALL> <SMALL>221 </SMALL> if value is None: <SMALL>222 </SMALL> value = '' <SMALL>223 </SMALL> return value <SMALL>224 </SMALL> <SMALL>225 </SMALL> <SMALL>226 </SMALL>def _ExpandValue(var, specials, params, name, default): <SMALL>227 </SMALL> """Expand one value. <SMALL>228 </SMALL> <SMALL>229 </SMALL> This expands the <field>.<field>...<field> part of the variable <SMALL>230 </SMALL> expansion. A field may be of the form *<param> to use the value <SMALL>231 </SMALL> of a parameter as the field name. <SMALL>232 </SMALL> """ <SMALL>233 </SMALL> if var == '_key': <SMALL>234 </SMALL> return name <SMALL>235 </SMALL> elif var == '_this': <SMALL>236 </SMALL> return params <SMALL>237 </SMALL> if var.startswith('_'): <SMALL>238 </SMALL> value = specials <SMALL>239 </SMALL> else: <SMALL>240 </SMALL> value = params <SMALL>241 </SMALL> <SMALL>242 </SMALL> for v in var.split('.'): <SMALL>243 </SMALL> if v == '*_this': <SMALL>244 </SMALL> v = params <SMALL>245 </SMALL> if v.startswith('*'): <SMALL>246 </SMALL> v = _GetValue(specials['_params'], v[1:]) <SMALL>247 </SMALL> if operator.isSequenceType(v): <SMALL>248 </SMALL> v = v[0] # reduce repeated url param to single value <SMALL>249 </SMALL> value = _GetValue(value, str(v), default) <SMALL>250 </SMALL> return value <SMALL>251 </SMALL> <SMALL>252 </SMALL> <SMALL>253 </SMALL>def _GetValue(collection, index, default=''): <SMALL>254 </SMALL> """Gets a single indexed value out of a collection. <SMALL>255 </SMALL> <SMALL>256 </SMALL> The index is either a key in a mapping or a numeric index into <SMALL>257 </SMALL> a sequence. <SMALL>258 </SMALL> <SMALL>259 </SMALL> Returns: <SMALL>260 </SMALL> value <SMALL>261 </SMALL> """ <SMALL>262 </SMALL> if operator.isMappingType(collection) and index in collection: <SMALL>263 </SMALL> value = collection[index] <SMALL>264 </SMALL> elif (operator.isSequenceType(collection) and index.isdigit() and <SMALL>265 </SMALL> int(index) < len(collection)): <SMALL>266 </SMALL> value = collection[int(index)] <SMALL>267 </SMALL> else: <SMALL>268 </SMALL> value = default <SMALL>269 </SMALL> return value <SMALL>270 </SMALL> <SMALL>271 </SMALL> <SMALL>272 </SMALL>def _Cond(test, if_true, if_false): <SMALL>273 </SMALL> """Substitute for 'if_true if test else if_false' in Python 2.4.""" <SMALL>274 </SMALL> if test: <SMALL>275 </SMALL> return if_true <SMALL>276 </SMALL> else: <SMALL>277 </SMALL> return if_false <SMALL>278 </SMALL> <SMALL>279 </SMALL> <SMALL>280 </SMALL>def _FindTag(template, open_marker, close_marker): <SMALL>281 </SMALL> """Finds a single tag. <SMALL>282 </SMALL> <SMALL>283 </SMALL> Args: <SMALL>284 </SMALL> template: the template to search. <SMALL>285 </SMALL> open_marker: the start of the tag (e.g., '{{'). <SMALL>286 </SMALL> close_marker: the end of the tag (e.g., '}}'). <SMALL>287 </SMALL> <SMALL>288 </SMALL> Returns: <SMALL>289 </SMALL> (tag, pos1, pos2) where the tag has the open and close markers <SMALL>290 </SMALL> stripped off and pos1 is the start of the tag and pos2 is the end of <SMALL>291 </SMALL> the tag. Returns (None, None, None) if there is no tag found. <SMALL>292 </SMALL> """ <SMALL>293 </SMALL> open_pos = template.find(open_marker) <SMALL>294 </SMALL> close_pos = template.find(close_marker, open_pos) <SMALL>295 </SMALL> if open_pos < 0 or close_pos < 0 or open_pos > close_pos: <SMALL>296 </SMALL> return (None, None, None) <SMALL>297 </SMALL> return (template[open_pos + len(open_marker):close_pos], <SMALL>298 </SMALL> open_pos, <SMALL>299 </SMALL> close_pos + len(close_marker)) <SMALL>300 </SMALL> <SMALL>301 </SMALL> <SMALL>302 </SMALL>def _Log(message): <SMALL>303 </SMALL> logging.warning('%s', message) <SMALL>304 </SMALL> print >>sys.stderr, message </PRE></BODY></HTML> |
| URL | http://google-gruyere.appspot.com/code/resources/dump.gtl |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 405 bytes. |
GET http://google-gruyere.appspot.com/code/resources/dump.gtl HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/code/?resources/dump.gtl Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 243 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: 7b20115ae030f495a6b3aaa34595cff1 Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 1337 |
| Response Body - size: 1,337 bytes. |
<HTML>
<STYLE> SMALL { color: #AAAAFF; font-size: 75% } BIG { color: #3333FF; font-size: 150%; font-weight: bold} </STYLE><BODY><PRE> <BIG>resources/dump.gtl</BIG> <SMALL> 1 </SMALL><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <SMALL> 2 </SMALL><!-- Copyright 2017 Google Inc. --> <SMALL> 3 </SMALL><html> <SMALL> 4 </SMALL><head> <SMALL> 5 </SMALL><title>Debug Dump</title> <SMALL> 6 </SMALL></head> <SMALL> 7 </SMALL><body> <SMALL> 8 </SMALL><table> <SMALL> 9 </SMALL> <tr> <SMALL> 10 </SMALL> <td valign='top'>_cookie:&nbsp;</td> <SMALL> 11 </SMALL> <td valign='top'>{{_cookie:pprint}}</td> <SMALL> 12 </SMALL> </tr> <SMALL> 13 </SMALL> <tr> <SMALL> 14 </SMALL> <td valign='top'>_profile:&nbsp;</td> <SMALL> 15 </SMALL> <td valign='top'>{{_profile:pprint}}</td> <SMALL> 16 </SMALL> </tr> <SMALL> 17 </SMALL> <tr> <SMALL> 18 </SMALL> <td valign='top'>_db:&nbsp;</td> <SMALL> 19 </SMALL> <td valign='top'>{{_db:pprint}}</td> <SMALL> 20 </SMALL> </tr> <SMALL> 21 </SMALL></table> <SMALL> 22 </SMALL></body> <SMALL> 23 </SMALL></html> </PRE></BODY></HTML> |
| URL | http://google-gruyere.appspot.com/code/resources/editprofile.gtl |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 419 bytes. |
GET http://google-gruyere.appspot.com/code/resources/editprofile.gtl HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/code/?resources/editprofile.gtl Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 243 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: 8c0c4184b5a7ce71183439e98835c508 Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 7482 |
| Response Body - size: 7,482 bytes. |
<HTML>
<STYLE> SMALL { color: #AAAAFF; font-size: 75% } BIG { color: #3333FF; font-size: 150%; font-weight: bold} </STYLE><BODY><PRE> <BIG>resources/editprofile.gtl</BIG> <SMALL> 1 </SMALL><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <SMALL> 2 </SMALL><!-- Copyright 2017 Google Inc. --> <SMALL> 3 </SMALL><html> <SMALL> 4 </SMALL><head> <SMALL> 5 </SMALL><title>Gruyere: Profile</title> <SMALL> 6 </SMALL>[[include:base.css]][[/include:base.css]] <SMALL> 7 </SMALL></head> <SMALL> 8 </SMALL> <SMALL> 9 </SMALL><body> <SMALL> 10 </SMALL>[[include:menubar.gtl]][[/include:menubar.gtl]] <SMALL> 11 </SMALL><div> <SMALL> 12 </SMALL><h2>Gruyere: Profile</h2> <SMALL> 13 </SMALL></div> <SMALL> 14 </SMALL> <SMALL> 15 </SMALL><div class='content'> <SMALL> 16 </SMALL>[[if:_cookie.is_admin]] <SMALL> 17 </SMALL> <h3>Add a new account or edit an existing account.</h3> <SMALL> 18 </SMALL>[[/if:_cookie.is_admin]] <SMALL> 19 </SMALL>[[if:!_cookie.is_admin]] <SMALL> 20 </SMALL> <h3>Edit your profile.</h3> <SMALL> 21 </SMALL>[[/if:!_cookie.is_admin]] <SMALL> 22 </SMALL>[[if:_message]] <SMALL> 23 </SMALL><div class='message'>{{_message}}</div> <SMALL> 24 </SMALL>[[/if:_message]] <SMALL> 25 </SMALL> <SMALL> 26 </SMALL><form method='get' action='/{{_unique_id}}/saveprofile'> <SMALL> 27 </SMALL><input type='hidden' name='action' value='update'> <SMALL> 28 </SMALL><table> <SMALL> 29 </SMALL> <tr><td> <SMALL> 30 </SMALL> User id: <SMALL> 31 </SMALL> </td><td> <SMALL> 32 </SMALL>[[if:_cookie.is_admin]] <SMALL> 33 </SMALL> [[if:uid]] <SMALL> 34 </SMALL> <input type='hidden' name='uid' value='{{uid.0}}'> <SMALL> 35 </SMALL> {{uid.0}} <SMALL> 36 </SMALL> [[/if:uid]] <SMALL> 37 </SMALL> [[if:!uid]] <SMALL> 38 </SMALL> <input type='hidden' name='uid' value='{{_cookie.uid}}'> <SMALL> 39 </SMALL> {{_cookie.uid}} <SMALL> 40 </SMALL> [[/if:!uid]] <SMALL> 41 </SMALL>[[/if:_cookie.is_admin]] <SMALL> 42 </SMALL>[[if:!_cookie.is_admin]] <SMALL> 43 </SMALL> [[if:_cookie.uid]] <SMALL> 44 </SMALL> {{_cookie.uid}} <SMALL> 45 </SMALL> [[/if:_cookie.uid]] <SMALL> 46 </SMALL> [[if:!_cookie.uid]] <SMALL> 47 </SMALL> &lt;not logged in&gt; <SMALL> 48 </SMALL> [[/if:!_cookie.uid]] <SMALL> 49 </SMALL>[[/if:!_cookie.is_admin]] <SMALL> 50 </SMALL> </td></tr> <SMALL> 51 </SMALL><tr><td> <SMALL> 52 </SMALL> User name: <SMALL> 53 </SMALL> </td><td> <SMALL> 54 </SMALL> <input type='text' <SMALL> 55 </SMALL> value='[[if:uid]]{{_db.*uid.name:text}}[[/if:uid]][[if:!uid]]{{_profile.name:text}}[[/if:!uid]]' <SMALL> 56 </SMALL> name='name' maxlength='16'> <SMALL> 57 </SMALL></td></tr> <SMALL> 58 </SMALL><tr><td> <SMALL> 59 </SMALL> OLD Password: <SMALL> 60 </SMALL> </td><td> <SMALL> 61 </SMALL> <input type='password' name='oldpw'> <SMALL> 62 </SMALL> </td><td> <SMALL> 63 </SMALL></td></tr> <SMALL> 64 </SMALL><tr><td> <SMALL> 65 </SMALL> NEW Password: <SMALL> 66 </SMALL> </td><td> <SMALL> 67 </SMALL> <input type='password' name='pw'> <SMALL> 68 </SMALL> <br><span style="color:red"><b>WARNING: Gruyere is not secure.<br> <SMALL> 69 </SMALL> Do not use a password that you use for any real service.</b></span> <SMALL> 70 </SMALL></td></tr> <SMALL> 71 </SMALL><tr><td> <SMALL> 72 </SMALL> Icon: <SMALL> 73 </SMALL> </td><td> <SMALL> 74 </SMALL> <input type='text' <SMALL> 75 </SMALL> value='[[if:uid]]{{_db.*uid.icon:text}}[[/if:uid]][[if:!uid]]{{_profile.icon:text}}[[/if:!uid]]' <SMALL> 76 </SMALL> value='{{_profile.icon:text}}' <SMALL> 77 </SMALL> name='icon'> <SMALL> 78 </SMALL> (32x32 image, URL to image location) <SMALL> 79 </SMALL></td></tr> <SMALL> 80 </SMALL><tr><td> <SMALL> 81 </SMALL> Homepage: <SMALL> 82 </SMALL> </td><td> <SMALL> 83 </SMALL> <input type='text' size='50' <SMALL> 84 </SMALL> value='[[if:uid]]{{_db.*uid.web_site:text}}[[/if:uid]][[if:!uid]]{{_profile.web_site:text}}[[/if:!uid]]' <SMALL> 85 </SMALL> name='web_site'> <SMALL> 86 </SMALL></td></tr> <SMALL> 87 </SMALL><tr><td> <SMALL> 88 </SMALL> Profile Color: <SMALL> 89 </SMALL> </td><td> <SMALL> 90 </SMALL> <input type='text' <SMALL> 91 </SMALL> value='[[if:uid]]{{_db.*uid.color:text}}[[/if:uid]][[if:!uid]]{{_profile.color:text}}[[/if:!uid]]' <SMALL> 92 </SMALL> name='color'> <SMALL> 93 </SMALL></td></tr> <SMALL> 94 </SMALL><tr><td> <SMALL> 95 </SMALL> Private Snippet: <SMALL> 96 </SMALL> </td><td> <SMALL> 97 </SMALL> <textarea name='private_snippet' rows='10' style='width:100%'>[[if:uid]]{{_db.*uid.private_snippet}}[[/if:uid]][[if:!uid]]{{_profile.private_snippet}}[[/if:!uid]]</textarea> <SMALL> 98 </SMALL></td></tr> <SMALL> 99 </SMALL>[[if:_cookie.is_admin]] <SMALL>100 </SMALL><tr><td> <SMALL>101 </SMALL> Is admin: <SMALL>102 </SMALL> </td><td> <SMALL>103 </SMALL> <input type='radio' <SMALL>104 </SMALL> [[if:uid]][[if:_db.*uid.is_admin]]checked[[/if:_db.*uid.is_admin]][[/if:uid]] <SMALL>105 </SMALL> [[if:!uid]][[if:_profile.is_admin]]checked[[/if:_profile.is_admin]][[/if:!uid]] <SMALL>106 </SMALL> name='is_admin' value='True'>Yes <SMALL>107 </SMALL> <input type='radio' <SMALL>108 </SMALL> [[if:uid]][[if:!_db.*uid.is_admin]]checked[[/if:!_db.*uid.is_admin]][[/if:uid]] <SMALL>109 </SMALL> [[if:!uid]][[if:!_profile.is_admin]]checked[[/if:!_profile.is_admin]][[/if:!uid]] <SMALL>110 </SMALL> name='is_admin' value='False'>No <SMALL>111 </SMALL></td></tr> <SMALL>112 </SMALL><tr><td> <SMALL>113 </SMALL> Is author: <SMALL>114 </SMALL> </td><td> <SMALL>115 </SMALL> <input type='radio' <SMALL>116 </SMALL> [[if:uid]][[if:_db.*uid.is_author]]checked[[/if:_db.*uid.is_author]][[/if:uid]] <SMALL>117 </SMALL> [[if:!uid]][[if:_profile.is_author]]checked[[/if:_profile.is_author]][[/if:!uid]] <SMALL>118 </SMALL> name='is_author' value='True'>Yes <SMALL>119 </SMALL> <input type='radio' <SMALL>120 </SMALL> [[if:uid]][[if:!_db.*uid.is_author]]checked[[/if:!_db.*uid.is_author]][[/if:uid]] <SMALL>121 </SMALL> [[if:!uid]][[if:!_profile.is_author]]checked[[/if:!_profile.is_author]][[/if:!uid]] <SMALL>122 </SMALL> name='is_author' value='False'>No <SMALL>123 </SMALL></td></tr> <SMALL>124 </SMALL>[[/if:_cookie.is_admin]] <SMALL>125 </SMALL> <SMALL>126 </SMALL><tr><td></td><td align='left'> <SMALL>127 </SMALL> <input type='submit' value='Update'> <SMALL>128 </SMALL></td></tr> <SMALL>129 </SMALL></table> <SMALL>130 </SMALL></form> <SMALL>131 </SMALL></div> <SMALL>132 </SMALL> <SMALL>133 </SMALL></body> <SMALL>134 </SMALL> <SMALL>135 </SMALL></html> <SMALL>136 </SMALL> </PRE></BODY></HTML> |
| URL | http://google-gruyere.appspot.com/code/resources/error.gtl |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 407 bytes. |
GET http://google-gruyere.appspot.com/code/resources/error.gtl HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/code/?resources/error.gtl Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 242 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: c5164fec213dd28a8a633a716d54cbfd Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 975 |
| Response Body - size: 975 bytes. |
<HTML>
<STYLE> SMALL { color: #AAAAFF; font-size: 75% } BIG { color: #3333FF; font-size: 150%; font-weight: bold} </STYLE><BODY><PRE> <BIG>resources/error.gtl</BIG> <SMALL> 1 </SMALL><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <SMALL> 2 </SMALL><!-- Copyright 2017 Google Inc. --> <SMALL> 3 </SMALL><html> <SMALL> 4 </SMALL><head> <SMALL> 5 </SMALL><title>Gruyere: Error</title> <SMALL> 6 </SMALL>[[include:base.css]][[/include:base.css]] <SMALL> 7 </SMALL></head> <SMALL> 8 </SMALL> <SMALL> 9 </SMALL><body> <SMALL> 10 </SMALL>[[include:menubar.gtl]][[/include:menubar.gtl]] <SMALL> 11 </SMALL> <SMALL> 12 </SMALL>[[if:_message]] <SMALL> 13 </SMALL><div class='message'>{{_message}}</div> <SMALL> 14 </SMALL>[[/if:_message]] <SMALL> 15 </SMALL> <SMALL> 16 </SMALL></body> <SMALL> 17 </SMALL> <SMALL> 18 </SMALL></html> </PRE></BODY></HTML> |
| URL | http://google-gruyere.appspot.com/code/resources/feed.gtl |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 405 bytes. |
GET http://google-gruyere.appspot.com/code/resources/feed.gtl HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/code/?resources/feed.gtl Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 243 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: b57dc5f16294ff477e59913fcd3d04a2 Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 1769 |
| Response Body - size: 1,769 bytes. |
<HTML>
<STYLE> SMALL { color: #AAAAFF; font-size: 75% } BIG { color: #3333FF; font-size: 150%; font-weight: bold} </STYLE><BODY><PRE> <BIG>resources/feed.gtl</BIG> <SMALL> 1 </SMALL>{{# Copyright 2017 Google Inc. }} <SMALL> 2 </SMALL>_feed(( <SMALL> 3 </SMALL>{{# with a uid parameter, get one user's snippets <SMALL> 4 </SMALL> returns [name, snippet, ...] <SMALL> 5 </SMALL>The first entry is the user's name and the remaining entries are <SMALL> 6 </SMALL>the user's snippets, in order from most recent to least recent. <SMALL> 7 </SMALL>}} <SMALL> 8 </SMALL>[[if:uid]] <SMALL> 9 </SMALL>[ <SMALL> 10 </SMALL>"[[if:_db.*uid]]{{_db.*uid.name}}[[/if:_db.*uid]][[if:!_db.*uid]]{{uid.0}}[[/if:!_db.*uid]]" <SMALL> 11 </SMALL>[[if:_db.*uid.snippets.0]][[for:_db.*uid.snippets]] <SMALL> 12 </SMALL>,"{{_this:html}}" <SMALL> 13 </SMALL>[[/for:_db.*uid.snippets]][[/if:_db.*uid.snippets.0]] <SMALL> 14 </SMALL>] <SMALL> 15 </SMALL>[[/if:uid]] <SMALL> 16 </SMALL>{{# without a uid parameter, get one snippet from each user <SMALL> 17 </SMALL> returns {'private_snippet':snippet, user:snippet, ...} <SMALL> 18 </SMALL>The first entry is the logged in user's private snippet. <SMALL> 19 </SMALL>The rest of the entries are all the other users' most recent snippet. <SMALL> 20 </SMALL>}} <SMALL> 21 </SMALL>[[if:!uid]] <SMALL> 22 </SMALL>{ <SMALL> 23 </SMALL>"private_snippet": <SMALL> 24 </SMALL> "{{_profile.private_snippet:html}}" <SMALL> 25 </SMALL>[[for:_db]] <SMALL> 26 </SMALL>[[if:is_author]][[if:snippets.0]],"{{_key}}": <SMALL> 27 </SMALL> "{{snippets.0:html}}"[[/if:snippets.0]][[/if:is_author]][[/for:_db]] <SMALL> 28 </SMALL>} <SMALL> 29 </SMALL>[[/if:!uid]] <SMALL> 30 </SMALL>)) </PRE></BODY></HTML> |
| URL | http://google-gruyere.appspot.com/code/resources/home.gtl |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 405 bytes. |
GET http://google-gruyere.appspot.com/code/resources/home.gtl HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/code/?resources/home.gtl Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 243 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: d1362b44f8aba0a5a87e45f8e9aa022b Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 3533 |
| Response Body - size: 3,533 bytes. |
<HTML>
<STYLE> SMALL { color: #AAAAFF; font-size: 75% } BIG { color: #3333FF; font-size: 150%; font-weight: bold} </STYLE><BODY><PRE> <BIG>resources/home.gtl</BIG> <SMALL> 1 </SMALL><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <SMALL> 2 </SMALL><!-- Copyright 2017 Google Inc. --> <SMALL> 3 </SMALL><html> <SMALL> 4 </SMALL><head> <SMALL> 5 </SMALL><title>Gruyere: Home</title> <SMALL> 6 </SMALL>[[include:base.css]][[/include:base.css]] <SMALL> 7 </SMALL><script src="/{{_unique_id}}/lib.js" text="text/javascript"> <SMALL> 8 </SMALL></script> <SMALL> 9 </SMALL></head> <SMALL> 10 </SMALL> <SMALL> 11 </SMALL><body> <SMALL> 12 </SMALL>[[include:menubar.gtl]][[/include:menubar.gtl]] <SMALL> 13 </SMALL><div> <SMALL> 14 </SMALL><h2 class='has-refresh'>Gruyere: Home</h2> <SMALL> 15 </SMALL><div class='refresh'><a class='button' onclick='_refreshHome("{{_unique_id}}")' href='#'>Refresh</a></div> <SMALL> 16 </SMALL></div> <SMALL> 17 </SMALL><div class='content'> <SMALL> 18 </SMALL><table width='100%'> <SMALL> 19 </SMALL>[[if:_profile.private_snippet]] <SMALL> 20 </SMALL> <tr> <SMALL> 21 </SMALL> <td></td> <SMALL> 22 </SMALL> <td> <SMALL> 23 </SMALL> Private&nbsp;snippet&nbsp; <SMALL> 24 </SMALL> </td> <SMALL> 25 </SMALL> <td> <SMALL> 26 </SMALL> <a class='button' id='show' onclick='_showHide("show", "hide")' href='#'>Show &#9658;</a> <SMALL> 27 </SMALL> <span id='hide' style='display: none;'> <SMALL> 28 </SMALL> <a class='button' onclick='_showHide("show", "hide")' href='#'>Hide &#9668;</a> &nbsp; <SMALL> 29 </SMALL> <span id='private_snippet'>{{_profile.private_snippet:html}}</span></span> <SMALL> 30 </SMALL> </td> <SMALL> 31 </SMALL> </tr> <SMALL> 32 </SMALL> <tr><td colspan='3'><hr></td></tr> <SMALL> 33 </SMALL>[[/if:_profile.private_snippet]] <SMALL> 34 </SMALL> <tr><td colspan='3'><b>Most recent snippets:</b></td></tr> <SMALL> 35 </SMALL>[[for:_db]] <SMALL> 36 </SMALL>[[if:is_author]][[if:snippets.0]] <SMALL> 37 </SMALL> <tr> <SMALL> 38 </SMALL> <td> <SMALL> 39 </SMALL> [[if:icon]]<img alt='' height='32' width='32' src='{{icon:text}}'>[[/if:icon]] <SMALL> 40 </SMALL> </td> <SMALL> 41 </SMALL> <td> <SMALL> 42 </SMALL> <b><span style='color:{{color:text}}'>[[if:name]]{{name:text}}[[/if:name]][[if:!name]]{{_key}}[[/if:!name]]</span></b> <SMALL> 43 </SMALL> </td> <SMALL> 44 </SMALL> <td width='100%'><span id='{{_key}}'>{{snippets.0:html}}</span> <SMALL> 45 </SMALL> <br> <SMALL> 46 </SMALL> <a href='/{{_unique_id}}/snippets.gtl?uid={{_key}}'>All snippets</a>&nbsp; <SMALL> 47 </SMALL> <a href='{{web_site:text}}'>Homepage</a> <SMALL> 48 </SMALL> <br> <SMALL> 49 </SMALL> <br> <SMALL> 50 </SMALL> </td> <SMALL> 51 </SMALL> </tr> <SMALL> 52 </SMALL>[[/if:snippets.0]][[/if:is_author]] <SMALL> 53 </SMALL>[[/for:_db]] <SMALL> 54 </SMALL></table> <SMALL> 55 </SMALL></div> <SMALL> 56 </SMALL></body> <SMALL> 57 </SMALL></html> </PRE></BODY></HTML> |
| URL | http://google-gruyere.appspot.com/code/resources/manage.gtl |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 409 bytes. |
GET http://google-gruyere.appspot.com/code/resources/manage.gtl HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/code/?resources/manage.gtl Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 243 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: decb989a995899df2e83431d769ef82d Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 1499 |
| Response Body - size: 1,499 bytes. |
<HTML>
<STYLE> SMALL { color: #AAAAFF; font-size: 75% } BIG { color: #3333FF; font-size: 150%; font-weight: bold} </STYLE><BODY><PRE> <BIG>resources/manage.gtl</BIG> <SMALL> 1 </SMALL><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <SMALL> 2 </SMALL><!-- Copyright 2017 Google Inc. --> <SMALL> 3 </SMALL><html> <SMALL> 4 </SMALL><head> <SMALL> 5 </SMALL><title>Gruyere: Profile</title> <SMALL> 6 </SMALL>[[include:base.css]][[/include:base.css]] <SMALL> 7 </SMALL></head> <SMALL> 8 </SMALL> <SMALL> 9 </SMALL><body> <SMALL> 10 </SMALL>[[include:menubar.gtl]][[/include:menubar.gtl]] <SMALL> 11 </SMALL><h2>Gruyere: Manage the server</h2> <SMALL> 12 </SMALL> <SMALL> 13 </SMALL><div class='content'> <SMALL> 14 </SMALL><form method='get' action='/{{_unique_id}}/editprofile.gtl'> <SMALL> 15 </SMALL><p>Edit a user's profile: <SMALL> 16 </SMALL><input type='text' name='uid'> <SMALL> 17 </SMALL> <input type='submit' value='Edit'> <SMALL> 18 </SMALL></p> <SMALL> 19 </SMALL></form> <SMALL> 20 </SMALL><p><a href='/{{_unique_id}}/reset'>Reset the server</a></p> <SMALL> 21 </SMALL><p><a href='/{{_unique_id}}/quitserver'>Quit the server</a></p> <SMALL> 22 </SMALL></div> <SMALL> 23 </SMALL></body> <SMALL> 24 </SMALL> <SMALL> 25 </SMALL></html> </PRE></BODY></HTML> |
| URL | http://google-gruyere.appspot.com/code/resources/menubar.gtl |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 411 bytes. |
GET http://google-gruyere.appspot.com/code/resources/menubar.gtl HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/code/?resources/menubar.gtl Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 243 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: aca5b0a9978b6b226d98eede8d8160d2 Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 1889 |
| Response Body - size: 1,889 bytes. |
<HTML>
<STYLE> SMALL { color: #AAAAFF; font-size: 75% } BIG { color: #3333FF; font-size: 150%; font-weight: bold} </STYLE><BODY><PRE> <BIG>resources/menubar.gtl</BIG> <SMALL> 1 </SMALL>{{# Copyright 2017 Google Inc. }} <SMALL> 2 </SMALL><div class='menu'> <SMALL> 3 </SMALL> <span id='menu-left'> <SMALL> 4 </SMALL> <a href='/{{_unique_id}}/'>Home</a> <SMALL> 5 </SMALL> [[if:_cookie.uid]] <SMALL> 6 </SMALL> | <a href='/{{_unique_id}}/snippets.gtl'>My&nbsp;Snippets</a> <SMALL> 7 </SMALL> | <a href='/{{_unique_id}}/newsnippet.gtl'>New&nbsp;Snippet</a> <SMALL> 8 </SMALL> | <a href='/{{_unique_id}}/upload.gtl'>Upload</a> <SMALL> 9 </SMALL> [[/if:_cookie.uid]] <SMALL> 10 </SMALL> </span> <SMALL> 11 </SMALL> <span id='menu-right'> <SMALL> 12 </SMALL> [[if:_cookie.uid]] <SMALL> 13 </SMALL> <span class='menu-user'> <SMALL> 14 </SMALL> {{_profile.name:text}} &lt;{{_cookie.uid}}&gt; <SMALL> 15 </SMALL> </span> <SMALL> 16 </SMALL> [[if:_cookie.is_admin]] <SMALL> 17 </SMALL> | <a href='/{{_unique_id}}/manage.gtl'>Manage this server</a> <SMALL> 18 </SMALL> [[/if:_cookie.is_admin]] <SMALL> 19 </SMALL> | <a href='/{{_unique_id}}/editprofile.gtl'>Profile</a> <SMALL> 20 </SMALL> | <a href='/{{_unique_id}}/logout'>Sign out</a> <SMALL> 21 </SMALL> [[/if:_cookie.uid]] <SMALL> 22 </SMALL> [[if:!_cookie.uid]] <SMALL> 23 </SMALL> <a href='/{{_unique_id}}/login'>Sign in</a> <SMALL> 24 </SMALL> | <a href='/{{_unique_id}}/newaccount.gtl'>Sign up</a> <SMALL> 25 </SMALL> [[/if:!_cookie.uid]] <SMALL> 26 </SMALL> </span> <SMALL> 27 </SMALL></div> </PRE></BODY></HTML> |
| URL | http://google-gruyere.appspot.com/code/sanitize.py |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 391 bytes. |
GET http://google-gruyere.appspot.com/code/sanitize.py HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/code/?sanitize.py Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 243 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: 4480b1f9f961edd2d73e281dec492f72 Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 4996 |
| Response Body - size: 4,996 bytes. |
<HTML>
<STYLE> SMALL { color: #AAAAFF; font-size: 75% } BIG { color: #3333FF; font-size: 150%; font-weight: bold} </STYLE><BODY><PRE> <BIG>sanitize.py</BIG> <SMALL> 1 </SMALL>"""HTML sanitizer for Gruyere, a web application with holes. <SMALL> 2 </SMALL> <SMALL> 3 </SMALL>Copyright 2017 Google Inc. All rights reserved. <SMALL> 4 </SMALL> <SMALL> 5 </SMALL>This code is licensed under the https://creativecommons.org/licenses/by-nd/3.0/us/ <SMALL> 6 </SMALL>Creative Commons Attribution-No Derivative Works 3.0 United States license. <SMALL> 7 </SMALL> <SMALL> 8 </SMALL>DO NOT COPY THIS CODE! <SMALL> 9 </SMALL> <SMALL> 10 </SMALL>This application is a small self-contained web application with numerous <SMALL> 11 </SMALL>security holes. It is provided for use with the Web Application Exploits and <SMALL> 12 </SMALL>Defenses codelab. You may modify the code for your own use while doing the <SMALL> 13 </SMALL>codelab but you may not distribute the modified code. Brief excerpts of this <SMALL> 14 </SMALL>code may be used for educational or instructional purposes provided this <SMALL> 15 </SMALL>notice is kept intact. By using Gruyere you agree to the Terms of Service <SMALL> 16 </SMALL>https://www.google.com/intl/en/policies/terms/ <SMALL> 17 </SMALL>""" <SMALL> 18 </SMALL> <SMALL> 19 </SMALL>__author__ = 'Bruce Leban' <SMALL> 20 </SMALL> <SMALL> 21 </SMALL># system modules <SMALL> 22 </SMALL>import re <SMALL> 23 </SMALL> <SMALL> 24 </SMALL> <SMALL> 25 </SMALL>def SanitizeHtml(s): <SMALL> 26 </SMALL> """Makes html safe for embedding in a document. <SMALL> 27 </SMALL> <SMALL> 28 </SMALL> Filters the html to exclude all but a small subset of html by <SMALL> 29 </SMALL> removing script tags/attributes. <SMALL> 30 </SMALL> <SMALL> 31 </SMALL> Args: <SMALL> 32 </SMALL> s: some html to sanitize. <SMALL> 33 </SMALL> <SMALL> 34 </SMALL> Returns: <SMALL> 35 </SMALL> The html with all unsafe html removed. <SMALL> 36 </SMALL> """ <SMALL> 37 </SMALL> processed = '' <SMALL> 38 </SMALL> while s: <SMALL> 39 </SMALL> start = s.find('<') <SMALL> 40 </SMALL> if start >= 0: <SMALL> 41 </SMALL> end = s.find('>', start) <SMALL> 42 </SMALL> if end >= 0: <SMALL> 43 </SMALL> before = s[:start] <SMALL> 44 </SMALL> tag = s[start:end+1] <SMALL> 45 </SMALL> after = s[end+1:] <SMALL> 46 </SMALL> else: <SMALL> 47 </SMALL> before = s[:start] <SMALL> 48 </SMALL> tag = s[start:] <SMALL> 49 </SMALL> after = '' <SMALL> 50 </SMALL> else: <SMALL> 51 </SMALL> before = s <SMALL> 52 </SMALL> tag = '' <SMALL> 53 </SMALL> after = '' <SMALL> 54 </SMALL> <SMALL> 55 </SMALL> processed += before + _SanitizeTag(tag) <SMALL> 56 </SMALL> s = after <SMALL> 57 </SMALL> return processed <SMALL> 58 </SMALL> <SMALL> 59 </SMALL> <SMALL> 60 </SMALL>TAG_RE = re.compile(r'<(.*?)(\s|>)') # matches the start of an html tag <SMALL> 61 </SMALL> <SMALL> 62 </SMALL> <SMALL> 63 </SMALL>def _SanitizeTag(t): <SMALL> 64 </SMALL> """Sanitizes a single html tag. <SMALL> 65 </SMALL> <SMALL> 66 </SMALL> This does both a 'whitelist' for <SMALL> 67 </SMALL> the allowed tags and a 'blacklist' for the disallowed attributes. <SMALL> 68 </SMALL> <SMALL> 69 </SMALL> Args: <SMALL> 70 </SMALL> t: a tag to sanitize. <SMALL> 71 </SMALL> <SMALL> 72 </SMALL> Returns: <SMALL> 73 </SMALL> a safe tag. <SMALL> 74 </SMALL> """ <SMALL> 75 </SMALL> allowed_tags = [ <SMALL> 76 </SMALL> 'a', 'b', 'big', 'br', 'center', 'code', 'em', 'h1', 'h2', 'h3', <SMALL> 77 </SMALL> 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'li', 'ol', 'p', 's', 'small', <SMALL> 78 </SMALL> 'span', 'strong', 'table', 'td', 'tr', 'u', 'ul', <SMALL> 79 </SMALL> ] <SMALL> 80 </SMALL> disallowed_attributes = [ <SMALL> 81 </SMALL> 'onblur', 'onchange', 'onclick', 'ondblclick', 'onfocus', <SMALL> 82 </SMALL> 'onkeydown', 'onkeypress', 'onkeyup', 'onload', 'onmousedown', <SMALL> 83 </SMALL> 'onmousemove', 'onmouseout', 'onmouseup', 'onreset', <SMALL> 84 </SMALL> 'onselect', 'onsubmit', 'onunload' <SMALL> 85 </SMALL> ] <SMALL> 86 </SMALL> <SMALL> 87 </SMALL> # Extract the tag name and make sure it's allowed. <SMALL> 88 </SMALL> if t.startswith('</'): <SMALL> 89 </SMALL> return t <SMALL> 90 </SMALL> m = TAG_RE.match(t) <SMALL> 91 </SMALL> if m is None: <SMALL> 92 </SMALL> return t <SMALL> 93 </SMALL> tag_name = m.group(1) <SMALL> 94 </SMALL> if tag_name not in allowed_tags: <SMALL> 95 </SMALL> t = t[:m.start(1)] + 'blocked' + t[m.end(1):] <SMALL> 96 </SMALL> <SMALL> 97 </SMALL> # This is a bit heavy handed but we want to be sure we don't <SMALL> 98 </SMALL> # allow any to get through. <SMALL> 99 </SMALL> for a in disallowed_attributes: <SMALL>100 </SMALL> t = t.replace(a, 'blocked') <SMALL>101 </SMALL> return t </PRE></BODY></HTML> |
| URL | http://google-gruyere.appspot.com/part1 |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 363 bytes. |
GET http://google-gruyere.appspot.com/part1 HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/ Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 244 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: 2299c1a98044c5397b549a259ee919b9 Date: Tue, 06 Feb 2024 17:42:18 GMT Server: Google Frontend Content-Length: 13686 |
| Response Body - size: 13,686 bytes. |
<HTML><HEAD><META http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<TITLE>Web Application Exploits and Defenses</TITLE> <LINK type="text/css" rel="stylesheet" href="../static/codelab.css"> <!--MARK-Z--> <SCRIPT> function toggleBlock(heading, whichID) { var image = heading.childNodes[0]; var block = document.getElementById(whichID); if (block) { if (getDisplay(block) == 'block') { block.style.display = 'none'; image.src = 'static/closed.gif'; } else { // "none" or "" block.style.display = 'block'; image.src = 'static/open.gif'; } } } function getDisplay(block) { var value = block.style.display; if (!value) { if (document.defaultView) { var computedStyle = document.defaultView.getComputedStyle(block, ""); value = computedStyle.getPropertyValue('display'); } else if (block.currentStyle) { value = block.currentStyle.display; } } return value; } </SCRIPT> </HEAD> <BODY bgcolor="#ffffff"> <DIV class="banner"></DIV> <H1><FONT size="+2"><IMG src="/static/gruyere-78.png" style="vertical-align:middle"> Web Application Exploits and Defenses (Part 1)</FONT> </H1> <P> A Codelab by Bruce Leban, Mugdha Bendre, and Parisa Tabriz </P> <DIV class="printable"> <DIV class="column1"> Table of Contents <UL> <LI class="L1" style="padding-top:0pt"> <A href="/#0__hackers">Beat the hackers</A></LI> <LI class="L1" style="padding-top:0pt"> <A href="/#0__gruyere">Gruyere</A></LI> <LI class="L1"> <A href="/part1#1__setup">Set-up</A> <UL> <LI class="L2"> <A href="/part1#1__reset_button">Reset Button</A></LI> <LI class="L2"> <A href="/part1#1__about_the_code">About the Code</A></LI> <LI class="L2"> <A href="/part1#1__features_and_technologies">Features and Technologies</A></LI> </UL></LI> <LI class="L1"> <A href="/part1#1__using_gruyere">Using Gruyere</A></LI> <LI class="L1"> <A href="/part2#2__cross_site_scripting">Cross-Site Scripting (XSS)</A> <UL> <LI class="L2"> <A href="/part2#2__xss_challenge">XSS Challenges</A></LI> <LI class="L2"> <A href="/part2#2__file_upload_xss">File Upload XSS</A></LI> <LI class="L2"> <A href="/part2#2__reflected_xss">Reflected XSS</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss">Stored XSS</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss_via_html_attribute">Stored XSS via HTML Attribute</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss_via_ajax">Stored XSS via AJAX</A></LI> <LI class="L2"> <A href="/part2#2__reflected_xss_via_ajax">Reflected XSS via AJAX</A></LI> <LI class="L2"> <A href="/part2#2__more_about_xss">More about XSS</A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__client_state_manipulation">Client-State Manipulation</A> <UL> <LI class="L2"> <A href="/part3#3__elevation_of_privilege"> Elevation of Privilege </A></LI> <LI class="L2"> <A href="/part3#3__cookie_manipulation"> Cookie Manipulation </A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__cross_site_request_forgery">Cross-Site Request Forgery (XSRF)</A> <UL> <LI class="L2"> <A href="/part3#3__xsrf_challenge">XSRF Challenge</A></LI> <LI class="L2"> <A href="/part3#3__more_about_preventing_xsrf">More about preventing XSRF</A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__cross_site_script_inclusion">Cross Site Script Inclusion (XSSI)</A> <UL> <LI class="L2"> <A href="/part3#3__xssi_challenge">XSSI Challenge</A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__path_traversal">Path Traversal</A> <UL> <LI class="L2"> <A href="/part4#4__information_disclosure_path_traversal">Information disclosure via path traversal </A></LI> <LI class="L2"> <A href="/part4#4__data_tampering_path_traversal">Data tampering via path traversal </A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__denial_of_service">Denial of Service</A> <UL> <LI class="L2"> <A href="/part4#4__dos_quit_server">DoS - Quit the Server</A></LI> <LI class="L2"> <A href="/part4#4__dos_overload_server">DoS - Overloading the Server</A></LI> <LI class="L2"> <A href="/part4#4__more_dos">More on Denial of Service</A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__code_execution">Code Execution</A> <UL> <LI class="L2"> <A href="/part4#4__code_execution_challenge">Code Execution Challenge</A></LI> <LI class="L2"> <A href="/part4#4__more_code_execution">More on Remote Code Execution</A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__configuration_vulnerabilities">Configuration Vulnerabilities</A> <UL> <LI class="L2"> <A href="/part5#5__information_disclosure_config_1">Information disclosure #1</A></LI> <LI class="L2"> <A href="/part5#5__information_disclosure_config_2">Information disclosure #2</A></LI> <LI class="L2"> <A href="/part5#5__information_disclosure_bug_3">Information disclosure #3 </A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__ajax_vulnerabilities">AJAX vulnerabilities</A> <UL> <LI class="L2"> <A href="/part5#5__dos_via_ajax">DoS via AJAX</A></LI> <LI class="L2"> <A href="/part5#5__phishing_via_ajax">Phishing via AJAX</A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__other_vulnerabilities"> Other Vulnerabilities </A> </H2> <UL> <LI class="L2"> <A href="/part5#5__buffer_and_integer_overflow">Buffer Overflow and Integer Overflow</A></LI> <LI class="L2"> <A href="/part5#5__sql_injection"> SQL Injection </A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__after_the_codelab">After the Codelab</A></LI> </UL> </DIV> <DIV class="column2"> <P></P> <P></P> <P> <NOAUTOLINK> </NOAUTOLINK></P> <H2><A name="1__setup"> </A> Setup </H2> <P> To access Gruyere, go to <CODE><A href="/start">https://google-gruyere.appspot.com/start</A></CODE>. AppEngine will start a new instance of Gruyere for you, assign it a unique id and redirect you to <CODE>https://google-gruyere.appspot.com/<!--do not replace-->123/</CODE> (where <CODE>123</CODE> is your unique id). Each instance of Gruyere is "sandboxed" from the other instances so your instance won't be affected by anyone else using Gruyere. You'll need to use your unique id instead of <CODE>123</CODE> in all the examples. If you want to share your instance of Gruyere with someone else (e.g., to show them a successful attack), just share the full URL with them including your unique id. </P> <P>The Gruyere source code is available online so that you can use it for white-box hacking. You can browse the source code at <A href="/code/">https://google-gruyere.appspot.com/code/</A> or download all the files from <A href="/gruyere-code.zip">https://google-gruyere.appspot.com/gruyere-code.zip</A>. If want to debug it or actually try fixing the bugs, you can download it and run it locally. You do not need to run Gruyere locally in order to do the lab. </P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'running_locally');"><IMG src="/static/closed.gif"> Running locally </H4> <DIV id="running_locally" style="display:none"> <P> <FONT color="#ff0000"> <B>WARNING:</B> Because Gruyere is very vulnerable, it includes some protection against being exploited by an external attacker when run locally. You'll see these parts of the code marked DO NOT CHANGE. Gruyere only accepts requests from localhost and uses a random unique id in the URL. However, it's difficult to fully protect against an external attack. And if you make changes to Gruyere you could make it more vulnerable to a real attack. Therefore, you should close other web pages while running Gruyere locally and you should make sure that no other user is logged in to the machine you are using. </FONT> </P> <P> To run Gruyere locally, you'll first need to install Python 2.7, if you don't already have it. Gruyere was developed and tested with version 2.7 and may not work with other versions of Python. You can download it from <A href="https://www.python.org/downloads/" target="_top">python.org</A>. Download Gruyere itself from <A href="/gruyere-code.zip">https://google-gruyere.appspot.com/gruyere-code.zip</A> and unpack it to your local disk. Then to run the application, simply type: </P> <P></P> <PRE> $ cd <gruyere-directory> $ ./gruyere.py</PRE> <P> You'll need to replace <CODE>google-gruyere.appspot.com</CODE> in all the examples with <CODE>localhost:8008</CODE> in addition to replacing <CODE>123</CODE> with your unique id. Note that the unique id appears in a different location. There are a few other small differences between running Gruyere locally vs. accessing the instance on App Engine. The most obvious is that the App Engine version runs in a limited sandbox. So if you do something that puts Gruyere into an infinite loop, the monitor will detect it and kill it. That might not happen when you run it locally, depending on what the loop is doing. </P> </DIV> </DIV> <BR> <H3><A name="1__reset_button"> </A> Reset Button </H3> As noted above, each instance is sandboxed so it can't consume infinite resources and it can't interfere with anyone else's instance. Notwithstanding that, it is possible to put your Gruyere instance into a state where it is completely unusable. If that happens, you can push a magic "reset button" to wipe out all the data in your instance and start from scratch. To do this, visit this URL with your instance id: <PRE> https://google-gruyere.appspot.com/resetbutton/382665580745386307547168512335551204731 </PRE> <BR> <H3><A name="1__about_the_code"> </A> About the Code </H3> <P> Gruyere is small and compact. Here is a quick rundown of the application code: </P><UL> <LI> <CODE><A href="/code/?gruyere.py">gruyere.py</A></CODE> is the main Gruyere web server </LI> <LI> <CODE><A href="/code/?data.py">data.py</A></CODE> stores the default data in the database. There is an administrator account and two default users. </LI> <LI> <CODE><A href="/code/?gtl.py">gtl.py</A></CODE> is the Gruyere template language </LI> <LI> <CODE><A href="/code/?sanitize.py">sanitize.py</A></CODE> is the Gruyere module used for sanitizing HTML to protect the application from security holes. </LI> <LI> <CODE><A href="/code/">resources/...</A></CODE> holds all template files, images, CSS, etc. </LI> </UL> <P></P> <BR> <H3><A name="1__features_and_technologies"> </A> Features and Technologies </H3> <P> Gruyere includes a number of special features and technologies which add attack surface. We'll highlight them here so you'll be aware of them as you try to attack it. Each of these introduces new vulnerabilities. </P> <P></P> <UL> <LI> HTML in Snippets: Users can include a limited subset of HTML in their snippets. </LI> <LI> File upload: Users can upload files to the server, e.g., to include pictures in their snippets. </LI> <LI> Web administration: System administrators can manage the system using a web interface. </LI> <LI> New accounts: Users can create their own accounts. </LI> <LI> Template language: Gruyere Template Language(GTL) is a new language that makes writing web pages easy as the templates connect directly to the database. Documentation for GTL can be found in <CODE><A href="/code/?gtl.py">gruyere/gtl.py</A></CODE>. </LI> <LI> AJAX: Gruyere uses AJAX to implement refresh on the home and snippets page. You should ignore the AJAX parts of Gruyere except for the challenges that specifically tell you to focus on AJAX. <UL> <LI> In a real application, refresh would probably happen automatically, but in Gruyere we've made it manual so that you can be in complete control while you are working with it. When you click the refresh link, Gruyere fetches <CODE><A href="/code/?resources/feed.gtl">feed.gtl</A></CODE> which contains refresh data for the current page and then client-side script uses the browser DOM API (Document Object Model) to insert the new snippets into the page. Since AJAX runs code on the client side, this script is visible to attackers who do not have access to your source code. </LI> </UL> </LI> </UL> <P></P> <BR> <H2><A name="1__using_gruyere"> </A> Using Gruyere </H2> <P> To familiarize yourself with the features of Gruyere, complete the following tasks: </P> <P></P> <UL> <LI> View another user's snippets by following the "All snippets" link on the main page. Also check out what they have their Homepage set to. </LI> <LI> Sign up for an account for yourself to use when hacking. <B>Do not use the same password for your Gruyere account as you use for any real service.</B> </LI> <LI> Fill in your account's profile, including a private snippet and an icon that will be displayed by your name. </LI> <LI> Create a snippet (via "New Snippet") containing your favorite joke. </LI> <LI> Upload a file (via "Upload") to your account. </LI> </UL> <P></P> <P> This covers the basic features provided by Gruyere. Now let's break them! </P> <BR><P></P> <FONT SIZE="+2"> <A href="/part2">Continue >></A> </FONT><BR> <BR> <P style="font-size:x-small"> © Google 2017 <A href="https://www.google.com/intl/en/policies/terms/">Terms of Service</A> <BR>The code portions of this codelab are licensed under the Creative Commons Attribution-No Derivative Works 3.0 United States license <<A href="https://creativecommons.org/licenses/by-nd/3.0/us/">https://creativecommons.org/licenses/by-nd/3.0/us</A>>. Brief excerpts of the code may be used for educational or instructional purposes provided this notice is kept intact. Except as otherwise noted the remainder of this codelab is licensed under the Creative Commons Attribution 3.0 United States license <<A href="https://creativecommons.org/licenses/by/3.0/us/">https://creativecommons.org/licenses/by/3.0/us</A>>. </P> </BODY></HTML> |
| URL | http://google-gruyere.appspot.com/part2 |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 363 bytes. |
GET http://google-gruyere.appspot.com/part2 HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/ Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 244 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: fb541a29ec594f4af66ea0f3cff34252 Date: Tue, 06 Feb 2024 17:42:21 GMT Server: Google Frontend Content-Length: 37564 |
| Response Body - size: 37,564 bytes. |
<HTML><HEAD><META http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<TITLE>Web Application Exploits and Defenses</TITLE> <LINK type="text/css" rel="stylesheet" href="../static/codelab.css"> <!--MARK-Z--> <SCRIPT> function toggleBlock(heading, whichID) { var image = heading.childNodes[0]; var block = document.getElementById(whichID); if (block) { if (getDisplay(block) == 'block') { block.style.display = 'none'; image.src = 'static/closed.gif'; } else { // "none" or "" block.style.display = 'block'; image.src = 'static/open.gif'; } } } function getDisplay(block) { var value = block.style.display; if (!value) { if (document.defaultView) { var computedStyle = document.defaultView.getComputedStyle(block, ""); value = computedStyle.getPropertyValue('display'); } else if (block.currentStyle) { value = block.currentStyle.display; } } return value; } </SCRIPT> </HEAD> <BODY bgcolor="#ffffff"> <DIV class="banner"></DIV> <H1><FONT size="+2"><IMG src="/static/gruyere-78.png" style="vertical-align:middle"> Web Application Exploits and Defenses (Part 2)</FONT> </H1> <P> A Codelab by Bruce Leban, Mugdha Bendre, and Parisa Tabriz </P> <DIV class="printable"> <DIV class="column1"> Table of Contents <UL> <LI class="L1" style="padding-top:0pt"> <A href="/#0__hackers">Beat the hackers</A></LI> <LI class="L1" style="padding-top:0pt"> <A href="/#0__gruyere">Gruyere</A></LI> <LI class="L1"> <A href="/part1#1__setup">Set-up</A> <UL> <LI class="L2"> <A href="/part1#1__reset_button">Reset Button</A></LI> <LI class="L2"> <A href="/part1#1__about_the_code">About the Code</A></LI> <LI class="L2"> <A href="/part1#1__features_and_technologies">Features and Technologies</A></LI> </UL></LI> <LI class="L1"> <A href="/part1#1__using_gruyere">Using Gruyere</A></LI> <LI class="L1"> <A href="/part2#2__cross_site_scripting">Cross-Site Scripting (XSS)</A> <UL> <LI class="L2"> <A href="/part2#2__xss_challenge">XSS Challenges</A></LI> <LI class="L2"> <A href="/part2#2__file_upload_xss">File Upload XSS</A></LI> <LI class="L2"> <A href="/part2#2__reflected_xss">Reflected XSS</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss">Stored XSS</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss_via_html_attribute">Stored XSS via HTML Attribute</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss_via_ajax">Stored XSS via AJAX</A></LI> <LI class="L2"> <A href="/part2#2__reflected_xss_via_ajax">Reflected XSS via AJAX</A></LI> <LI class="L2"> <A href="/part2#2__more_about_xss">More about XSS</A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__client_state_manipulation">Client-State Manipulation</A> <UL> <LI class="L2"> <A href="/part3#3__elevation_of_privilege"> Elevation of Privilege </A></LI> <LI class="L2"> <A href="/part3#3__cookie_manipulation"> Cookie Manipulation </A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__cross_site_request_forgery">Cross-Site Request Forgery (XSRF)</A> <UL> <LI class="L2"> <A href="/part3#3__xsrf_challenge">XSRF Challenge</A></LI> <LI class="L2"> <A href="/part3#3__more_about_preventing_xsrf">More about preventing XSRF</A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__cross_site_script_inclusion">Cross Site Script Inclusion (XSSI)</A> <UL> <LI class="L2"> <A href="/part3#3__xssi_challenge">XSSI Challenge</A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__path_traversal">Path Traversal</A> <UL> <LI class="L2"> <A href="/part4#4__information_disclosure_path_traversal">Information disclosure via path traversal </A></LI> <LI class="L2"> <A href="/part4#4__data_tampering_path_traversal">Data tampering via path traversal </A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__denial_of_service">Denial of Service</A> <UL> <LI class="L2"> <A href="/part4#4__dos_quit_server">DoS - Quit the Server</A></LI> <LI class="L2"> <A href="/part4#4__dos_overload_server">DoS - Overloading the Server</A></LI> <LI class="L2"> <A href="/part4#4__more_dos">More on Denial of Service</A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__code_execution">Code Execution</A> <UL> <LI class="L2"> <A href="/part4#4__code_execution_challenge">Code Execution Challenge</A></LI> <LI class="L2"> <A href="/part4#4__more_code_execution">More on Remote Code Execution</A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__configuration_vulnerabilities">Configuration Vulnerabilities</A> <UL> <LI class="L2"> <A href="/part5#5__information_disclosure_config_1">Information disclosure #1</A></LI> <LI class="L2"> <A href="/part5#5__information_disclosure_config_2">Information disclosure #2</A></LI> <LI class="L2"> <A href="/part5#5__information_disclosure_bug_3">Information disclosure #3 </A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__ajax_vulnerabilities">AJAX vulnerabilities</A> <UL> <LI class="L2"> <A href="/part5#5__dos_via_ajax">DoS via AJAX</A></LI> <LI class="L2"> <A href="/part5#5__phishing_via_ajax">Phishing via AJAX</A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__other_vulnerabilities"> Other Vulnerabilities </A> </H2> <UL> <LI class="L2"> <A href="/part5#5__buffer_and_integer_overflow">Buffer Overflow and Integer Overflow</A></LI> <LI class="L2"> <A href="/part5#5__sql_injection"> SQL Injection </A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__after_the_codelab">After the Codelab</A></LI> </UL> </DIV> <DIV class="column2"> <P></P> <P></P> <P> <NOAUTOLINK> </NOAUTOLINK></P> <H2><A name="2__cross_site_scripting"> </A> Cross-Site Scripting (XSS) </H2> <P> Cross-site scripting (XSS) is a vulnerability that permits an attacker to inject code (typically HTML or JavaScript) into contents of a website not under the attacker's control. When a victim views such a page, the injected code executes in the victim's browser. Thus, the attacker has bypassed the browser's <A href="https://www.google.com/search?q=same+origin+policy">same origin policy</A> and can steal victim's private information associated with the website in question. </P> <P> In a <b>reflected XSS</b> attack, the attack is in the request itself (frequently the URL) and the vulnerability occurs when the server inserts the attack in the response verbatim or incorrectly escaped or sanitized. The victim triggers the attack by browsing to a malicious URL created by the attacker. In a <b>stored XSS</b> attack, the attacker stores the attack in the application (e.g., in a snippet) and the victim triggers the attack by browsing to a page on the server that renders the attack, by not properly escaping or sanitizing the stored data. </P> <P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_desc');"><IMG src="/static/closed.gif"> More details </H4> <DIV id="xss_desc" style="display:none"> <P></P> <P> To understand how this could happen: suppose the URL <CODE>https://www.google.com/search?q=flowers</CODE> returns a page containing the HTML fragment </P> <P></P> <PRE> <p>Your search for 'flowers' returned the following results:</p> </PRE> <P> that is, the value of the query parameter <CODE>q</CODE> is inserted verbatim into the page returned by Google. If <CODE>www.google.com</CODE> did not do any validation or escaping of <CODE>q</CODE> (it does), an attacker could craft a link that looks like this:<BR> <PRE> https://www.google.com/search?q=flowers+%3Cscript%3Eevil_script()%3C/script%3E </PRE> and trick a victim into clicking on this link. When a victim loads this link, the following page gets rendered in the victim's browser: </P> <P></P> <PRE> <p>Your search for 'flowers<script>evil_script()</script>' returned the following results:</p> </PRE> <P> And the browser executes <CODE>evil_script()</CODE>. And since the page comes from <CODE>www.google.com</CODE>, <CODE>evil_script()</CODE> is executed in the context of <CODE>www.google.com</CODE> and has access to all the victim's browser state and cookies for that domain. </P> <P> Note that the victim does not even need to explicitly click on the malicious link. Suppose the attacker owns <CODE>www.evil.example.com</CODE>, and creates a page with an <CODE><iframe></CODE> pointing to the malicious link; if the victim visits <CODE>www.evil.example.com</CODE>, the attack will silently be activated. </P> </DIV> </DIV> <P></P> <H3><A name="2__xss_challenge"> </A> XSS Challenges </H3> <P> Typically, if you can get JavaScript to execute on a page when it's viewed by another user, you have an XSS vulnerability. A simple JavaScript function to use when hacking is the <CODE>alert()</CODE> function, which creates a pop-up box with whatever string you pass as an argument. </P> <P>You might think that inserting an alert message isn't terribly dangerous, but if you can inject that, you can inject other scripts that are more malicious. It is not necessary to be able to inject any particular special character in order to attack. If you can inject <CODE>alert(1)</CODE> then you can inject arbitrary script using <CODE>eval(String.fromCharCode(...))</CODE>. </P> <P> Your challenge is to find XSS vulnerabilities in Gruyere. You should look for vulnerabilities both in URLs and in stored data. Since XSS vulnerabilities usually involve applications not properly handling untrusted user data, a common method of attack is to enter random text in input fields and look at how it gets rendered in the response page's HTML source. But before we do that, let's try something simpler. </P> <P></P> <H3><A name="2__file_upload_xss"> </A> File Upload XSS </H3> <P> <IMG src="/static/cheese_b.png" style="vertical-align:middle; padding-right: 5px;"> <B>Can you upload a file that allows you to execute arbitrary script on the <CODE>google-gruyere.appspot.com</CODE> domain?</B> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'upload_xss_hint1');"><IMG src="/static/closed.gif"> Hint </H4> <DIV id="upload_xss_hint1" style="display:none"> <P> You can upload HTML files and HTML files can contain script. </P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'upload_xss_sol');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="upload_xss_sol" style="display:none"> <P><B>To exploit,</B> upload a <CODE>.html</CODE> file containing a script like this: <PRE> <script> alert(document.cookie); </script> </PRE> </P><P> <B>To fix,</B> host the content on a separate domain so the script won't have access to any content from your domain. That is, instead of hosting user content on <CODE>example.com/<I>username</I></CODE> we would host it at <CODE><I>username</I>.usercontent.example.com</CODE> or <CODE><I>username</I>.example-usercontent.com</CODE>. (Including something like "<CODE>usercontent</CODE>" in the domain name avoids attackers registering usernames that look innocent like <CODE>wwww</CODE> and using them for phishing attacks.) <P> </DIV> </DIV> <H3><A name="2__reflected_xss"> </A> Reflected XSS </H3> <P> There's an interesting problem here. Some browsers have built-in protection against reflected XSS attacks. There are also browser extensions like NoScript that provide some protection. If you're using one of those browsers or extensions, you may need to use a different browser or temporarily disable the extension to execute these attacks. </P> <P>At the time this codelab was written, the two browsers which had this protection were IE and Chrome. To work around this, Gruyere automatically includes a <TT>X-XSS-Protection: 0</TT> HTTP header in every response which is recognized by IE and will be recognized by future versions of Chrome. (It's available in the developer channel now.) If you're using Chrome, you can try starting it with the <TT>--disable-xss-auditor</TT> flag by entering one of these commands: <UL><LI>Windows: <TT>"C:\Documents and Settings\USERNAME\Local Settings\Application Data\Google\Chrome\Application\chrome.exe" --disable-xss-auditor</TT> <LI>Mac: <TT>/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --disable-xss-auditor</TT> <LI>GNU/Linux: <TT>/opt/google/chrome/google-chrome --disable-xss-auditor</TT> </UL> If you're using Firefox with the NoScript extension, add <TT>google-gruyere.appspot.com</TT> to the allow list. If you still can't get the XSS attacks to work, try a different browser. </P> <P>You may think that you don't need to worry about XSS if the browser protects against it. The truth is that the browser protection can't be perfect because it doesn't really know your application and therefore there may be ways for a clever hacker to circumvent that protection. The real protection is to not have an XSS vulnerability in your application in the first place. </P> <IMG src="/static/cheese_b.png" style="vertical-align:middle; padding-right: 5px;"> <B>Find a reflected XSS attack. What we want is a URL that when clicked on will execute a script.</B> <P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_url_hint1');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="xss_url_hint1" style="display:none"> <P> What does this URL do? </P> <P></P> <PRE> https://google-gruyere.appspot.com/382665580745386307547168512335551204731/invalid </PRE> <P></P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_url_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="xss_url_hint2" style="display:none"> <P> The most dangerous characters in a URL are <CODE><</CODE> and <CODE>></CODE>. If you can get an application to directly insert what you want in a page and can get those characters through, then you can probably get a script through. Try these: </P> <P></P> <PRE> https://google-gruyere.appspot.com/382665580745386307547168512335551204731/%3e%3c https://google-gruyere.appspot.com/382665580745386307547168512335551204731/%253e%253c https://google-gruyere.appspot.com/382665580745386307547168512335551204731/%c0%be%c0%bc https://google-gruyere.appspot.com/382665580745386307547168512335551204731/%26gt;%26lt; https://google-gruyere.appspot.com/382665580745386307547168512335551204731/%26amp;gt;%26amp;lt; https://google-gruyere.appspot.com/382665580745386307547168512335551204731/\074\x3c\u003c\x3C\u003C\X3C\U003C https://google-gruyere.appspot.com/382665580745386307547168512335551204731/+ADw-+AD4- </PRE> <P> This tries <CODE>></CODE> and <CODE><</CODE> in many different ways that might be able to make it through the URL and get rendered incorrectly using: verbatim (URL %-encoding), double %-encoding, bad UTF-8 encoding, HTML &-encoding, double &-encoding, and several different variations on C-style encoding. View the resulting source and see if any of those work. (Note: literally typing <CODE>><</CODE> in the URL is identical to <CODE>%3e%3c</CODE> because the browser automatically %-encodes those character. If you are trying to want a literal <CODE>></CODE> or <CODE><</CODE> then you will need to use a tool like curl to send those characters in URL.) </P> <P></P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_url_sol');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="xss_url_sol" style="display:none"> <P> <B>To exploit,</B> create a URL like the following and get a victim to click on it: </P> <P></P> <PRE> https://google-gruyere.appspot.com/382665580745386307547168512335551204731/<script>alert(1)</script> </PRE> <P> <B>To fix,</B> you need to escape user input that is displayed in error messages. Error messages are displayed using <CODE><A href="/code/?resources/error.gtl">error.gtl</A></CODE>, but are not escaped in the template. The part of the template that renders the message is <CODE>{{message}}</CODE> and it's missing the modifier that tells it to escape user input. Add the <CODE>:text</CODE> modifier to escape the user input: </P><PRE> <div class="message">{{_message:text}}</div> </PRE> <P> This flaw would have been best mitigated by a design that escapes all output by default and only displays raw HTML when explicitly tagged to do so. There are also <A href="https://www.google.com/search?q=XSS+auto+escaping" target="_top">autoescaping</A> features available in many template systems. </P> <!--MARK-2--> <P></P> </DIV> </DIV> <P></P> <H3><A name="2__stored_xss"> </A> Stored XSS </H3> <P> <IMG src="/static/cheese_b.png" style="vertical-align:middle; padding-right: 5px;"> <B>Now find a stored XSS. What we want to do is put a script in a place where Gruyere will serve it back to another user.</B> </P> The most obvious place that Gruyere serves back user-provided data is in a snippet (ignoring uploaded files which we've already discussed.)<P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_stored_hint1');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="xss_stored_hint1" style="display:none"> <P> Put this in a snippet and see what you get: </P> <P></P> <PRE> <script>alert(1)</script> </PRE> <P> There are many different ways that script can be embedded in a document. </P> <P></P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_stored_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="xss_stored_hint2" style="display:none"> <P> Hackers don't limit themselves to valid HTML syntax. Try some invalid HTML and see what you get. You may need to experiment a bit in order to find something that will work. There are multiple ways to do this. </P> <P></P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_stored_sol');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="xss_stored_sol" style="display:none"> <P> <B>To exploit,</B> enter any of these as your snippet (there are certainly more methods): </P> <P></P> <PRE> (1) <a onmouseover="alert(1)" href="#">read this!</a> (2) <p <script>alert(1)</script>hello (3) </td <script>alert(1)</script>hello </PRE> <P> Notice that there are multiple failures in sanitizing the HTML. Snippet 1 worked because <CODE>onmouseover</CODE> was inadvertently omitted from the list of disallowed attributes in <CODE><A href="/code/?sanitize.py">sanitize.py</A></CODE>. Snippets 2 and 3 work because browsers tend to be forgiving with HTML syntax and the handling of both start and end tags is buggy. </P> <P> <B>To fix,</B> we need to investigate and fix the sanitizing performed on the snippets. Snippets are sanitized in <CODE>_SanitizeTag</CODE> in the <CODE><A href="/code/?sanitize.py">sanitize.py</A></CODE> file. Let's block snippet 1 by adding <CODE>"onmouseover"</CODE> to the list of <CODE>disallowed_attributes</CODE>. </P> <P> <B>Oops!</B> This doesn't completely solve the problem. Looking at the code that was just fixed, can you find a way to bypass the fix? </P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_stored_not_fixed_hint');"><IMG src="/static/closed.gif"> Hint </H4> <DIV id="xss_stored_not_fixed_hint" style="display:none"> <P> Take a close look at the code in <CODE>_SanitizeTag</CODE> that determines whether or not an HTML attribute is allowed or not. </P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_stored_not_fixed');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="xss_stored_not_fixed" style="display:none"> <P> The fix was insufficient because the code that checks for disallowed attributes is case sensitive and HTML is not. So this still works: </P> <P></P> <PRE> (1') <a ONMOUSEOVER="alert(1)" href="#">read this!</a> </PRE> <P> Correctly sanitizing HTML is a tricky problem. The <CODE>_SanitizeTag</CODE> function has a number of critical design flaws: </P> <P></P> <UL> <LI> It does not validate the well-formedness of the input HTML. As we see, badly formed HTML passes through the sanitizer unchanged. Since browsers typically apply very lenient parsing, it is very hard to predict the browser's interpretation of the given HTML unless we exercise strict control on its format. </LI> <LI> It uses blacklisting of attributes, which is a bad technique. One of our exploits got past the blacklist simply by using an uppercase version of the attribute. There could be other attributes <A href="https://www.w3.org/TR/html40/index/attributes.html" target="_top">missing from this list</A> that are dangerous. It is always better to whitelist known good values. </LI> <LI> The sanitizer does not do any further sanitization of attribute values. This is dangerous since URI attributes like <CODE>href</CODE> and <CODE>src</CODE> and the <CODE>style</CODE> attribute can all be used to inject JavaScript. </LI> </UL> <P> The right approach to HTML sanitization is to: </P><UL> <LI> Parse the input into an intermediate DOM structure, then rebuild the body as well-formed output. </LI> <LI> Use strict whitelists for allowed tags and attributes. </LI> <LI> Apply strict sanitization of URL and CSS attributes if they are permitted. </LI> </UL> <P>Whenever possible it is preferable to use an already available known and proven <A href="https://www.google.com/search?q=sanitize+html" target="_top">HTML sanitizer</A>. </P> <!--MARK-3--> <P></P> </DIV> </DIV> </DIV> <P></P> <H3><A name="2__stored_xss_via_html_attribute"> </A> Stored XSS via HTML Attribute </H3> <P> <IMG src="/static/cheese_b.png" style="vertical-align:middle; padding-right: 5px;"> <B>You can also do XSS by injecting a value into an HTML attribute. Inject a script by setting the color value in a profile.</B> </P><DIV style="margin-left:18pt"> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_color_hint');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="xss_color_hint" style="display:none"> <P> The color is rendered as <CODE>style='color:<I>color</I>'</CODE>. Try including a single quote character in your color name. </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_color_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="xss_color_hint2" style="display:none"> <P> You can insert an HTML attribute that executes a script. </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_color_sol');"><IMG src="/static/closed.gif"> Exploit and Fixes </H4> <DIV id="xss_color_sol" style="display:none"> <P> <B>To exploit,</B> use the following for your color preference: </P> <P></P> <PRE> red' onload='alert(1)' onmouseover='alert(2) </PRE> <P> You may need to move the mouse over the snippet to trigger the attack. This attack works because the first quote ends the <CODE>style</CODE> attribute and the second quote starts the onload attribute. </P> <P> But this attack shouldn't work at all. Take a look at <CODE><A href="/code/?resources/home.gtl">home.gtl</A></CODE> where it renders the color. It says <CODE>style='{{color:text}}'</CODE> and as we saw earlier, the <CODE>:text</CODE> part tells it to escape text. So why doesn't this get escaped? In <CODE><A href="/code/?gtl.py">gtl.py</A></CODE>, it calls <CODE>cgi.escape(str(value))</CODE> which takes an optional second parameter that indicates that the value is being used in an HTML attribute. So you can replace this with <CODE>cgi.escape(str(value),True)</CODE>. Except that doesn't fix it! The problem is that <CODE>cgi.escape</CODE> assumes your HTML attributes are enclosed in double quotes and this file is using single quotes. (This should teach you to always carefully read the documentation for libraries you use and to always test that they do what you want.) </P> <P> You'll note that this attack uses both <CODE>onload</CODE> and <CODE>onmouseover</CODE>. That's because even though W3C specifies that onload events is only supported on <CODE>body</CODE> and <CODE>frameset</CODE> elements, some browsers support them on other elements. So if the victim is using one of those browsers, the attack always succeeds. Otherwise, it succeeds when the user moves the mouse. It's not uncommon for attackers to use multiple attack vectors at the same time. </P> <P> <B>To fix,</B> we need to use a correct text escaper, that escapes single and double quotes too. Add the following function to <CODE><A href="/code/?gtl.py">gtl.py</A></CODE> and call it instead of <CODE>cgi.escape</CODE> for the <CODE>text</CODE> escaper. </P> <P></P> <PRE> def _EscapeTextToHtml(var): """Escape HTML metacharacters. This function escapes characters that are dangerous to insert into HTML. It prevents XSS via quotes or script injected in attribute values. It is safer than cgi.escape, which escapes only <, >, & by default. cgi.escape can be told to escape double quotes, but it will never escape single quotes. """ meta_chars = { '"': '&quot;', '\'': '&#39;', # Not &apos; '&': '&amp;', '<': '&lt;', '>': '&gt;', } escaped_var = "" for i in var: if i in meta_chars: escaped_var = escaped_var + meta_chars[i] else: escaped_var = escaped_var + i return escaped_var </PRE> <P> <B>Oops!</B> This doesn't completely solve the problem. Even with the above fix in place, the color value is still vulnerable. <H4 style="cursor: pointer" onclick="toggleBlock(this, 'another_style_xss_hint1');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="another_style_xss_hint1" style="display:none"> <P> Some browsers allow you to include script in stylesheets. </P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'another_style_xss_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="another_style_xss_hint2" style="display:none"> <P> The easiest browser to exploit in this way is Internet Explorer which supports dynamic CSS properties. </P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'another_style_xss_sol');"><IMG src="/static/closed.gif"> Another Exploit and Fix </H4> <DIV id="another_style_xss_sol" style="display:none"> <P> Internet Explorer's dynamic CSS properites (aka CSS expressions) make this attack particularly easy. </P> <B>To exploit,</B> use the following for your color preference: <PRE> expression(alert(1)) </PRE> <P> While other browsers don't support CSS expressions, there are other dangerous CSS properties, such as Mozilla's <CODE>-moz-binding</CODE>. </P> <P> <B>To fix,</B> we need to sanitize the color as a color. The best thing to do would be to add a new output sanitizing form to gtl, i.e., we would write <CODE>{{foo:color}}</CODE> which makes sure <CODE>foo</CODE> is safe to use as a color. This function can be used to sanitize: </P> <PRE> SAFE_COLOR_RE = re.compile(r"^#?[a-zA-Z0-9]*$") def _SanitizeColor(color): """Sanitizes a color, returning 'invalid' if it's invalid. A valid value is either the name of a color or # followed by the hex code for a color (like #FEFFFF). Returning an invalid value value allows a style sheet to specify a default value by writing 'color:default; color:{{foo:color}}'. """ if SAFE_COLOR_RE.match(color): return color return 'invalid' </PRE> <P> Colors aren't the only values we might want to allow users to provide. You should do similar sanitizing for user-provided fonts, sizes, urls, etc. It's helpful to do input validation, so that when a user enters an invalid value, you'll reject it at that time. But only doing input validation would be a mistake: if you find an error in your validation code or a new browser exposes a new attack vector, you'd have to go back and scrub all previously entered values. Or, you could add the output validation which you should have been doing in the first place. </DIV> </P></DIV> </DIV> <P></P> <H3><A name="2__stored_xss_via_ajax"> </A> Stored XSS via AJAX </H3> <P> <IMG src="/static/cheese_b.png" style="vertical-align:middle; padding-right: 5px;"> <B>Find an XSS attack that uses a bug in Gruyere's AJAX code.</B> The attack should be triggered when you click the refresh link on the page. </P> <P></P> <DIV style="margin-left:18pt"> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_ajax_hint');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="xss_ajax_hint" style="display:none"> <P> Run <CODE>curl</CODE> on <CODE>https://google-gruyere.appspot.com/382665580745386307547168512335551204731/feed.gtl</CODE> and look at the result. (Or browse to it in your browser and view source.) You'll see that it includes each user's first snippet into the response. This entire response is then evaluated on the client side which then inserts the snippets into the document. Can you put something in your snippet that will be parsed differently than expected? </P> <P></P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_ajax_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="xss_ajax_hint2" style="display:none"> <P> Try putting some quotes (<CODE>"</CODE>) in your snippet. </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_ajax_sol');"><IMG src="/static/closed.gif"> Exploit and Fixes </H4> <DIV id="xss_ajax_sol" style="display:none"> <P> <B>To exploit,</B> Put this in your snippet: </P> <P></P> <PRE> all <span style=display:none>" + (alert(1),"") + "</span>your base </PRE> <P> The JSON should look like <PRE>_feed(({..., "Mallory": "snippet", ...}))</PRE> but instead looks like this: <PRE>_feed({..., "Mallory": <U>"all <span style=display:none>"</U> + <U>(alert(1),"")</U> + <U>"</span>your base"</U>, ...})</PRE> Each underlined part is a separate expression. Note that this exploit is written to be invisible both in the original page rendering (because of the <CODE><span style=display:none></CODE>) and after refresh (because it inserts only an empty string). All that will appear on the screen is <A href="https://www.google.com/search?q=all+your+base+are+belong+to+us" target="_top">all your base</A>. There are bugs on both the server and client sides which enable this attack. </P> <P> <B>To fix,</B> first, on the server side, the text is incorrectly escaped when it is rendered in the JSON response. The template says <CODE>{{snippet.0:html}}</CODE> but that's not enough. This text is going to be inserted into the innerHTML of a DOM node so the HTML does have to be sanitized. However, that sanitized text is then going to be inserted into JavaScript and single and double quotes have to be escaped. That is, adding support for <CODE>{{...:js}}</CODE> to GTL would not be sufficient; we would also need to support something like <CODE>{{...:html:js}}</CODE>. </P><P>To escape quotes, use <CODE>\x27</CODE> and <CODE>\x22</CODE> for single and double quote respectively. Replacing them with <CODE>&#27;</CODE> and <CODE>&quot;</CODE> is incorrect as those are not recognized in JavaScript strings and will break quotes around HTML attribute. </P> <P> Second, in the browser, Gruyere converts the JSON by using JavaScript's <CODE>eval</CODE>. In general, <CODE>eval</CODE> is very dangerous and should rarely be used. If it used, it must be used very carefully, which is hardly the case here. We should be using the JSON parser which ensures that the string does not include any unsafe content. The JSON parser is available at <A href="http://www.json.org/" target="_top">json.org</A>. </P> <P></P> </DIV> </DIV> <P></P> <H3><A name="2__reflected_xss_via_ajax"> </A> Reflected XSS via AJAX </H3> <P> <IMG src="/static/cheese_b.png" style="vertical-align:middle; padding-right: 5px;"><B>Find a URL that when clicked on will execute a script using one of Gruyere's AJAX features.</B> </P> <P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_ajax2_hint');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="xss_ajax2_hint" style="display:none"> <P> When Gruyere refreshes a user snippets page, it uses <PRE>https://google-gruyere.appspot.com/382665580745386307547168512335551204731/feed.gtl?uid=value</PRE> and the result is the script <PRE>_feed((["user", "snippet1", ... ]))</PRE> </P> <P></P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_ajax2_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="xss_ajax2_hint2" style="display:none"> <P> This uses a different vulnerability, but the exploit is very similar to the previous reflected XSS exploit. </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_ajax2_sol');"><IMG src="/static/closed.gif"> Exploit and Fixes </H4> <DIV id="xss_ajax2_sol" style="display:none"> <P> <B>To exploit,</B> create a URL like the following and get a victim to click on it: </P> <P></P> <PRE> https://google-gruyere.appspot.com/382665580745386307547168512335551204731/feed.gtl?uid=<script>alert(1)</script> https://google-gruyere.appspot.com/382665580745386307547168512335551204731/feed.gtl?uid=%3Cscript%3Ealert(1)%3C/script%3E </PRE> <P> This renders as <PRE>_feed((["<script>alert(1)</script>"]))</PRE> which surprisingly <I>does</I> execute the script. The bug is that Gruyere returns all gtl files as content type <CODE>text/html</CODE> and browsers are very tolerant of what HTML files they accept. </P> <P> <B>To fix,</B> you need to make sure that your JSON content can never be interpreted as HTML. Even though literal <CODE><</CODE> and <CODE>></CODE> are allowed in JavaScript strings, you need to make sure they don't appear literally where a browser can misinterpret them. Thus, you'd need to modify <CODE>{{...:js}}</CODE> to replace them with the JavaScript escapes <CODE>\x3c</CODE> and <CODE>\x3e</CODE>. It is always safe to write <CODE>'\x3c\x3e'</CODE> in Javscript strings instead of <CODE>'<>'</CODE>. (And, as noted above, using the HTML escapes <CODE>&lt;</CODE> and <CODE>&gt;</CODE> is incorrect.) </P> <P> You should also always set the content type of your responses, in this case serving JSON results as <CODE>application/javascript.</CODE> This alone doesn't solve the problem because browsers don't always respect the content type: browsers sometimes do "sniffing" to try to "fix" results from servers that don't provide the correct content type. </P> <P><B>But wait, there's more!</B> Gruyere doesn't set the content encoding either. And some browsers try to guess what the encoding type of a document is or an attacker may be able to embed content in a document that defines the content type. So, for example, if an attacker can trick the browser into thinking a document is <CODE><A href="https://www.google.com/search?q=utf-7">UTF-7</A></CODE> then it could embed a script tag as <CODE>+ADw-script+AD4-</CODE> since <CODE>+ADw-</CODE> and <CODE>+AD4-</CODE> are alternate encodings for <CODE><</CODE> and <CODE>></CODE>. So always set both the content type <I>and</I> the content encoding of your responses, e.g., for HTML:</P> <PRE> Content-Type: text/html; charset=utf-8 </PRE> </DIV> </DIV> <H3><A name="2__more_about_xss"> </A> More about XSS </H3> <P> In addition to the XSS attacks described above, there are quite a few more ways to attack Gruyere with XSS. Collect them all! </P> <P> XSS is a difficult beast. On one hand, a fix to an XSS vulnerability is usually trivial and involves applying the correct sanitizing function to user input when it's displayed in a certain context. On the other hand, if history is any indication, this is extremely difficult to get right. <A href="https://www.kb.cert.org/vuls/" target="_top">US-CERT</A> reports dozens of publicly disclosed XSS vulnerabilities involving multiple companies. </P> <P> Though there is no magic defense to getting rid of XSS vulnerabilities, here are some steps you should take to prevent these types of bugs from popping up in your products: </P> <P></P> <OL> <LI> First, make sure you <A href="https://www.google.com/search?q=understanding+cross-site+scripting" target="_top">understand the problem</A>. </LI> <LI> Wherever possible, do sanitizing via templates features instead of calling escaping functions in source code. This way, all of your escaping is done in one place and your product can benefit from security technologies designed for template systems that verify their correctness or actually do the escaping for you. Also, familiarize yourself with the other security features of your template system. </LI> <LI> Employ good testing practices with respect to XSS. </LI> <LI> Don't write your own template library :) </LI> </OL> <!--MARK-6--> <BR><P></P> <FONT SIZE="+2"> <A href="/part3">Continue >></A> </FONT><BR> <BR> <P style="font-size:x-small"> © Google 2017 <A href="https://www.google.com/intl/en/policies/terms/">Terms of Service</A> <BR>The code portions of this codelab are licensed under the Creative Commons Attribution-No Derivative Works 3.0 United States license <<A href="https://creativecommons.org/licenses/by-nd/3.0/us/">https://creativecommons.org/licenses/by-nd/3.0/us</A>>. Brief excerpts of the code may be used for educational or instructional purposes provided this notice is kept intact. Except as otherwise noted the remainder of this codelab is licensed under the Creative Commons Attribution 3.0 United States license <<A href="https://creativecommons.org/licenses/by/3.0/us/">https://creativecommons.org/licenses/by/3.0/us</A>>. </P> </BODY></HTML> |
| URL | http://google-gruyere.appspot.com/part3 |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 363 bytes. |
GET http://google-gruyere.appspot.com/part3 HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/ Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 244 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: f16a707de42dff20c808e8c075a2e977 Date: Tue, 06 Feb 2024 17:42:21 GMT Server: Google Frontend Content-Length: 29253 |
| Response Body - size: 29,253 bytes. |
<HTML><HEAD><META http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<TITLE>Web Application Exploits and Defenses</TITLE> <LINK type="text/css" rel="stylesheet" href="../static/codelab.css"> <!--MARK-Z--> <SCRIPT> function toggleBlock(heading, whichID) { var image = heading.childNodes[0]; var block = document.getElementById(whichID); if (block) { if (getDisplay(block) == 'block') { block.style.display = 'none'; image.src = 'static/closed.gif'; } else { // "none" or "" block.style.display = 'block'; image.src = 'static/open.gif'; } } } function getDisplay(block) { var value = block.style.display; if (!value) { if (document.defaultView) { var computedStyle = document.defaultView.getComputedStyle(block, ""); value = computedStyle.getPropertyValue('display'); } else if (block.currentStyle) { value = block.currentStyle.display; } } return value; } </SCRIPT> </HEAD> <BODY bgcolor="#ffffff"> <DIV class="banner"></DIV> <H1><FONT size="+2"><IMG src="/static/gruyere-78.png" style="vertical-align:middle"> Web Application Exploits and Defenses (Part 3)</FONT> </H1> <P> A Codelab by Bruce Leban, Mugdha Bendre, and Parisa Tabriz </P> <DIV class="printable"> <DIV class="column1"> Table of Contents <UL> <LI class="L1" style="padding-top:0pt"> <A href="/#0__hackers">Beat the hackers</A></LI> <LI class="L1" style="padding-top:0pt"> <A href="/#0__gruyere">Gruyere</A></LI> <LI class="L1"> <A href="/part1#1__setup">Set-up</A> <UL> <LI class="L2"> <A href="/part1#1__reset_button">Reset Button</A></LI> <LI class="L2"> <A href="/part1#1__about_the_code">About the Code</A></LI> <LI class="L2"> <A href="/part1#1__features_and_technologies">Features and Technologies</A></LI> </UL></LI> <LI class="L1"> <A href="/part1#1__using_gruyere">Using Gruyere</A></LI> <LI class="L1"> <A href="/part2#2__cross_site_scripting">Cross-Site Scripting (XSS)</A> <UL> <LI class="L2"> <A href="/part2#2__xss_challenge">XSS Challenges</A></LI> <LI class="L2"> <A href="/part2#2__file_upload_xss">File Upload XSS</A></LI> <LI class="L2"> <A href="/part2#2__reflected_xss">Reflected XSS</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss">Stored XSS</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss_via_html_attribute">Stored XSS via HTML Attribute</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss_via_ajax">Stored XSS via AJAX</A></LI> <LI class="L2"> <A href="/part2#2__reflected_xss_via_ajax">Reflected XSS via AJAX</A></LI> <LI class="L2"> <A href="/part2#2__more_about_xss">More about XSS</A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__client_state_manipulation">Client-State Manipulation</A> <UL> <LI class="L2"> <A href="/part3#3__elevation_of_privilege"> Elevation of Privilege </A></LI> <LI class="L2"> <A href="/part3#3__cookie_manipulation"> Cookie Manipulation </A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__cross_site_request_forgery">Cross-Site Request Forgery (XSRF)</A> <UL> <LI class="L2"> <A href="/part3#3__xsrf_challenge">XSRF Challenge</A></LI> <LI class="L2"> <A href="/part3#3__more_about_preventing_xsrf">More about preventing XSRF</A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__cross_site_script_inclusion">Cross Site Script Inclusion (XSSI)</A> <UL> <LI class="L2"> <A href="/part3#3__xssi_challenge">XSSI Challenge</A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__path_traversal">Path Traversal</A> <UL> <LI class="L2"> <A href="/part4#4__information_disclosure_path_traversal">Information disclosure via path traversal </A></LI> <LI class="L2"> <A href="/part4#4__data_tampering_path_traversal">Data tampering via path traversal </A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__denial_of_service">Denial of Service</A> <UL> <LI class="L2"> <A href="/part4#4__dos_quit_server">DoS - Quit the Server</A></LI> <LI class="L2"> <A href="/part4#4__dos_overload_server">DoS - Overloading the Server</A></LI> <LI class="L2"> <A href="/part4#4__more_dos">More on Denial of Service</A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__code_execution">Code Execution</A> <UL> <LI class="L2"> <A href="/part4#4__code_execution_challenge">Code Execution Challenge</A></LI> <LI class="L2"> <A href="/part4#4__more_code_execution">More on Remote Code Execution</A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__configuration_vulnerabilities">Configuration Vulnerabilities</A> <UL> <LI class="L2"> <A href="/part5#5__information_disclosure_config_1">Information disclosure #1</A></LI> <LI class="L2"> <A href="/part5#5__information_disclosure_config_2">Information disclosure #2</A></LI> <LI class="L2"> <A href="/part5#5__information_disclosure_bug_3">Information disclosure #3 </A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__ajax_vulnerabilities">AJAX vulnerabilities</A> <UL> <LI class="L2"> <A href="/part5#5__dos_via_ajax">DoS via AJAX</A></LI> <LI class="L2"> <A href="/part5#5__phishing_via_ajax">Phishing via AJAX</A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__other_vulnerabilities"> Other Vulnerabilities </A> </H2> <UL> <LI class="L2"> <A href="/part5#5__buffer_and_integer_overflow">Buffer Overflow and Integer Overflow</A></LI> <LI class="L2"> <A href="/part5#5__sql_injection"> SQL Injection </A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__after_the_codelab">After the Codelab</A></LI> </UL> </DIV> <DIV class="column2"> <P></P> <P></P> <P> <NOAUTOLINK> </NOAUTOLINK></P> <H2><A name="3__client_state_manipulation"> </A> Client-State Manipulation </H2> <P> When a user interacts with a web application, they do it indirectly through a browser. When the user clicks a button or submits a form, the browser sends a request back to the web server. Because the browser runs on a machine that can be controlled by an attacker, the application must not trust any data sent by the browser. </P><P> It might seem that not trusting any user data would make it impossible to write a web application but that's not the case. If the user submits a form that says they wish to purchase an item, it's OK to trust that data. But if the submitted form also includes the price of the item, that's something that cannot be trusted. </P> <P></P> <H3><A name="3__elevation_of_privilege"> </A> Elevation of Privilege </H3> <IMG src="/static/cheese_w.png" style="vertical-align:middle; padding-right: 5px;"> <B>Convert your account to an administrator account.</B> <P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'elevation_hint');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="elevation_hint" style="display:none"> <P> Take a look at the <CODE><A href="/code/?resources/editprofile.gtl">editprofile.gtl</A></CODE> page that users and administrators use to edit profile settings. If you're not an administrator, the page looks a bit different. Can you figure out how to fool Gruyere into letting you use this page to update your account? </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'elevation_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="elevation_hint2" style="display:none"> <P> Can you figure out how to fool Gruyere into <i>thinking</i> you used this page to update your account? </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'clientstate');"><IMG src="/static/closed.gif"> Exploit and Fixes </H4> <DIV id="clientstate" style="display:none"> <P> You can convert your account to being an administrator by issuing either of the following requests: </P><UL> <LI> <CODE>https://google-gruyere.appspot.com/382665580745386307547168512335551204731/saveprofile?action=update&is_admin=True</CODE> </LI> <LI> <CODE>https://google-gruyere.appspot.com/382665580745386307547168512335551204731/saveprofile?action=update&is_admin=True&uid=username</CODE> (which will make any <CODE>username</CODE> into an an admin) </LI> </UL> <P>After visiting this URL, your account is now marked as an administrator but your cookie still says you're not. So sign out and back in to get a new cookie. After logging in, notice the 'Manage this server' link on the top right.</P> <P>The bug here is that there is no validation on the server side that the request is authorized. The only part of the code that restricts the changes that a user is allowed to make are in the template, hiding parts of the UI that they shouldn't have access to. The correct thing to do is to check for authorization on the server, at the time that the request is received. </P> </DIV> </DIV> <P></P> <H3><A name="3__cookie_manipulation"> </A> Cookie Manipulation </H3> Because the HTTP protocol is stateless, there's no way a web server can automatically know that two requests are from the same user. For this reason, <A href="https://www.google.com/search?q=http+cookies">cookies</a> were invented. When a web site includes a cookie (an arbitrary string) in a HTTP response, the browser automatically sends the cookie back to the browser on the next request. Web sites can use the cookie to save session state. Gruyere uses cookies to remember the identity of the logged in user. Since the cookie is stored on the client side, it's vulnerable to manipulation. Gruyere protects the cookies from manipulation by adding a hash to it. Notwithstanding the fact that this hash isn't very good protection, you don't need to break the hash to execute an attack. </P><P> <IMG src="/static/cheese_bw.png" style="vertical-align:middle; padding-right: 5px;"> <B>Get Gruyere to issue you a cookie for someone else's account.</B> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'cookie_hint');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="cookie_hint" style="display:none"> <P> You don't need to look at the Gruyere cookie parsing code. You just need to know what the cookies look like. Gruyere's cookies use the format: <PRE> <i>hash</i>|<i>username</i>|admin|author </PRE> </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'cookie_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="cookie_hint2" style="display:none"> <P> Gruyere issues a cookie when you log in. Can you trick it into issuing you a cookie that looks like another user's cookie? </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'cookieparsing');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="cookieparsing" style="display:none"> <P> You can get Gruyere to issue you a cookie for someone else's account by creating a new account with username <CODE>"foo|admin|author"</CODE>. When you log into this account, it will issue you the cookie <CODE>"hash|foo|admin|author||author"</CODE> which actually logs you into <CODE>foo</CODE> as an administrator. (So this is also an elevation of privilege attack.) </P> <P>Having no restrictions on the characters allowed in usernames means that we have to be careful when we handle them. In this case, the cookie parsing code is tolerant of malformed cookies and it shouldn't be. It should escape the username when it constructs the cookie and it should reject a cookie if it doesn't match the exact pattern it is expecting. </P> <P>Even if we fix this, Python's hash function is not cryptographically secure. If you look at Python's <CODE>string_hash</CODE> function in <CODE><a href="https://svn.python.org/projects/python/trunk/Objects/stringobject.c">python/Objects/stringobject.cc</A></CODE> you'll see that it hashes the string strictly from left to right. That means that we don't need to know the cookie secret to generate our own hashes; all we need is another string that hashes to the same value, which we can find in a relatively short time on a typical PC. In contrast, with a cryptographic hash function, changing any bit of the string will change many bits of the hash value in an unpredictable way. At a minimum, you should use a secure hash function to protect your cookies. You should also consider encrypting the entire cookie as plain text cookies can expose information you might not want exposed. </P> <P>And these cookies are also vulnerable to a replay attack. Once a user is issued a cookie, it's good forever and there's no way to revoke it. So if a user is an administrator at one time, they can save the cookie and continue to act as an administrator even if their administrative rights are taken away. While it's convenient to not have to make a database query in order to check whether or not a user is an administrator, that might be too dangerous a detail to store in the cookie. If avoiding additional database access is important, the server could cache a list of recent admin users. Including a timestamp in a cookie and expiring it after some period of time also mitigates against a replay attack.</P> <P><B>Another challenge:</B> Since account names are limited to 16 characters, it seems that this trick would not work to log in to the actual <CODE>administrator</CODE> account since <CODE>"administrator|admin"</CODE> is 19 characters. Can you figure out how to bypass that restriction? </P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'cookie2');"><IMG src="/static/closed.gif"> Additional Exploit and Fix </H4> <DIV id="cookie2" style="display:none"> The 16 character limit is implemented on the client side. Just issue your own request: <PRE> https://google-gruyere.appspot.com/382665580745386307547168512335551204731/saveprofile?action=new&uid=administrator|admin|author&pw=secret </PRE> <P>Again, this restriction should be implemented on the server side, not just the client side.</P> </DIV> </DIV> </DIV> <BR><BR> <H2><A name="3__cross_site_request_forgery"> </A> Cross-Site Request Forgery (XSRF) </H2> <P> The previous section said "If the user submits a form that says they wish to purchase an item, it's OK to trust that data." That's true as long as it really was the user that submitted the form. If your site is vulnerable to XSS, then the attacker can fake any request as if it came from the user. But even if you've protected against XSS, there's another attack that you need to protect against: cross-site request forgery. </P><P> When a browser makes requests to a site, it always sends along any cookies it has for that site, regardless of where the request comes from. Additionally, web servers generally cannot distinguish between a request initiated by a deliberate user action (e.g., user clicking on "Submit" button) versus a request made by the browser without user action (e.g., request for an embedded image in a page). Therefore, if a site receives a request to perform some action (like deleting a mail, changing contact address), it cannot know whether this action was knowingly initiated by the user — even if the request contains authentication cookies. An attacker can use this fact to fool the server into performing actions the user did not intend to perform. </P> <P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xsrf_desc');"><IMG src="/static/closed.gif"> More details </H4> <DIV id="xsrf_desc" style="display:none"> <P> For example, suppose Blogger is vulnerable to XSRF attacks (it isn't). And let us say Blogger has a Delete Blog button on the dashboard that points to this URL: <PRE> https://www.blogger.com/deleteblog.do?blogId=BLOGID </PRE> Bob, the attacker, embeds the following HTML on his web page on <CODE>https://www.evil.example.com</CODE>: </P> <PRE> <img src="https://www.blogger.com/deleteblog.do?blogId=alice's-blog-id" style="display:none"> </PRE> <P> If the victim, Alice, is logged in to <CODE>www.blogger.com</CODE> when she views the above page, here is what happens: </P> <P></P> <UL> <LI> Her browser loads the page from <CODE>https://www.evil.example.com</CODE>. The browser then tries to load all embedded objects in the page, including the <CODE>img</CODE> shown above. </LI> <LI> The browser makes a request to <CODE>https://www.blogger.com/deleteblog.do?blogId=alice's-blog-id</CODE> to load the image. Since Alice is logged into Blogger — that is, she has a Blogger cookie — the browser also sends that cookie in the request. </LI> <LI> Blogger verifies the cookie is a valid session cookie for Alice. It verifies that the blog referenced by <CODE>alice's-blog-id</CODE> is owned by Alice. It deletes Alice's blog. </LI> <LI> Alice has no idea what hit her. </LI> </UL> <P> In this sample attack, since each user has their own blog id, the attack has to be specifically targeted to a single person. In many cases, though, requests like these don't contain any user-specific data. </P></DIV> </DIV> <P></P> <H3><A name="3__xsrf_challenge"> </A> XSRF Challenge </H3> <P> The goal here is to find a way to perform an account changing action on behalf of a logged in Gruyere user without their knowledge. Assume you can get them to visit a web page under your control. </P> <P> <IMG src="/static/cheese_b.png" style="vertical-align:middle; padding-right: 5px;"> <B> Find a way to get someone to delete one of their Gruyere snippets.</B> </P> <P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xsrf_hint');"><IMG src="/static/closed.gif"> Hint </H4> <DIV id="xsrf_hint" style="display:none"> <P> What is the URL used to delete a snippet? Look at the URL associated with the "X" next to a snippet. </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xsrf_soln');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="xsrf_soln" style="display:none"> <P> <B>To exploit,</B> lure a user to visit a page that makes the following request: </P> <P></P> <PRE> https://google-gruyere.appspot.com/382665580745386307547168512335551204731/deletesnippet?index=0 </PRE> <P> To be especially sneaky, you could set your Gruyere icon to this URL and the victim would be exploited when they visited the main page. </P> <P> <B>To fix,</B> we should first change <CODE>/deletesnippet</CODE> to work via a <CODE>POST</CODE> request since this is a state changing action. In the HTML form, change <CODE>method='get'</CODE> to <CODE>method='post'</CODE>. On the server side, <CODE>GET</CODE> and <CODE>POST</CODE> requests look the same except that they usually call different handlers. For example, Gruyere uses Python's BaseHTTPServer which calls <CODE>do_GET</CODE> for <CODE>GET</CODE> requests and <CODE>do_POST</CODE> for <CODE>POST</CODE> requests. </P><P> <B>However</B>, note that changing to <CODE>POST</CODE> is not enough of a fix in itself! (Gruyere uses <CODE>GET</CODE> requests exclusively because it makes hacking it a bit easier. <CODE>POST</CODE> is not more secure than <CODE>GET</CODE> but it is more correct: browsers may re-issue <CODE>GET</CODE> requests which can result in an action getting executed more than once; browsers won't reissue <CODE>POST</CODE> requests without user consent.) Then we need to pass a unique, unpredictable authorization token to the user and require that it get sent back before performing the action. For this authorization token, <CODE>action_token</CODE>, we can use a hash of the value of the user's cookie appended to a current timestamp and include this token in all state-changing HTTP requests as an additional HTTP parameter. The reason we use <CODE>POST</CODE> over <CODE>GET</CODE> requests is that if we pass <CODE>action_token</CODE> as a URL parameter, it might leak via HTTP Referer headers. The reason we include the timestamp in our hash is so that we can expire old tokens, which mitigates the risk if it leaks. </P> <P> When a request is processed, Gruyere should regenerate the token and compare it with the value supplied with the request. If the values are equal, then it should perform the action. Otherwise, it should reject it. The functions that generate and verify the tokens look like this: </P> <P></P> <PRE> def _GenerateXsrfToken(self, cookie): """Generates a timestamp and XSRF token for all state changing actions.""" timestamp = time.time() return timestamp + "|" + (str(hash(cookie_secret + cookie + timestamp))) def _VerifyXsrfToken(self, cookie, action_token): """Verifies an XSRF token included in a request.""" # First, make sure that the token isn't more than a day old. (action_time, action_hash) = action_token.split("|", 1) now = time.time() if now - 86400 > float(action_time): return False # Second, regenerate it and check that it matches the user supplied value hash_to_verify = str(hash(cookie_secret + cookie + action_time) return action_hash == hash_to_verify </PRE> <P> <B>Oops!</B> There's several things wrong with these functions. </P><H4 style="cursor: pointer" onclick="toggleBlock(this, 'xsrf_whats_wrong');"><IMG src="/static/closed.gif"> What's missing? </H4> <DIV id="xsrf_whats_wrong" style="display:none"> <P> By including the time in the token, we prevent it from being used forever, but if an attacker were to gain access to a copy of the token, they could reuse it as many times as they wanted within that 24 hour period. The expiration time of a token should be set to a small value that represents the reasonable length of time it will take the user to make a request. This token also doesn't protect against an attack where a token for one request is intercepted and then used for a different request. As suggested by the name <CODE>action_token</CODE>, the token should be tied to the specific state changing action being performed, such as the URL of the page. A better signature for <CODE>_GenerateXsrfToken</CODE> would be <CODE>(self, cookie, action)</CODE>. For very long actions, like editing snippets, a script on the page could query the server to update the token when the user hits submit. (But read the next section about XSSI to make sure that an attacker won't be able to read that new token.)</P> <P> XSRF vulnerabilities exist because an attacker can easily script a series of requests to an application and than force a user to execute them by visiting some page. To prevent this type of attack, you need to introduce some value that can't be predicted or scripted by an attacker for <B>every account changing</B> request. Some application frameworks have XSRF protection built in: they automatically include a unique token in every response and verify it on every POST request. Other frameworks provide functions that you can use to do that. If neither of these cases apply, then you'll have to <A href="https://www.google.com/search?q=preventing+(XSRF+OR+CSRF)" target="_top">build your own</A>. Be careful of things that don't work: using <CODE>POST</CODE> instead of <CODE>GET</CODE> is advisable but not sufficient by itself, checking Referer headers is insufficient, and copying cookies into hidden form fields can make your cookies less secure. </P> <!--MARK-7--> </DIV> </DIV> <BR><P></P> </DIV> <H2><A name="3__cross_site_script_inclusion"> </A> Cross Site Script Inclusion (XSSI) </H2> <P> Browsers prevent pages of one domain from reading pages in other domains. But they do not prevent pages of a domain from referencing resources in other domains. In particular, they allow images to be rendered from other domains and scripts to be executed from other domains. An included script doesn't have its own security context. It runs in the security context of the page that included it. For example, if <CODE>www.evil.example.com</CODE> includes a script hosted on <CODE>www.google.com</CODE> then that script runs in the <CODE>evil</CODE> context not in the <CODE>google</CODE> context. So any user data in that script will "leak." </P> <!--MARK-8--> <P></P> <H3><A name="3__xssi_challenge"> </A> XSSI Challenge </H3> <P> <IMG src="/static/cheese_w.png" style="vertical-align:middle; padding-right: 5px;"> <B>Find a way to read someone else's private snippet using XSSI.</B> </P> <P> That is, create a page on another web site and put something in that page that can read your private snippet. (You don't need to post it to a web site: you can just create a <CODE>.html</CODE> in your home directory and double click on it to open in a browser.) </P> <P></P> <DIV style="margin-left:18pt"> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xssi_hint1');"><IMG src="/static/closed.gif"> Hint 1 </H4> <P></P> <DIV id="xssi_hint1" style="display:none"> <P> You can run a script from another domain by adding <PRE> <SCRIPT src="https://google-gruyere.appspot.com/382665580745386307547168512335551204731/..."></SCRIPT> </PRE> to your HTML file. What scripts does Gruyere have? </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xssi_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="xssi_hint2" style="display:none"> <P> <CODE><A href="/code/?resources/feed.gtl">feed.gtl</A></CODE> is a script. Given that, how can you get the private snippet out of the script? </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xssi_sol');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="xssi_sol" style="display:none"> <P> <B>To exploit,</B> put this in an html file: </P> <P></P> <PRE> <script> function _feed(s) { alert("Your private snippet is: " + s['private_snippet']); } </script> <script src="https://google-gruyere.appspot.com/382665580745386307547168512335551204731/feed.gtl"></script> </PRE> <P> When the script in <CODE><A href="/code/?resources/feed.gtl">feed.gtl</A></CODE> is executed, it runs in the context of the attacker's web page and uses the <CODE>_feed</CODE> function which can do whatever it wants with the data, including sending it off to another web site. </P> <P> You might think that you can fix this by eliminating the function call and just having the bare expression. That way, when the script is executed by inclusion, the response will be evaluated and then discarded. That won't work because JavaScript allows you to do things like redefine default constructors. So when the object is evaluated, the hosting page's constructors are invoked, which can do whatever they want with the values. </P> <!--MARK-9--> <P> <B>To fix,</B> there are several changes you can make. Any one of these changes will prevent currently possible attacks, but if you add several layers of protection ("<a href="https://www.google.com/search?q=%22defense+in+depth%22+security">defense in depth</a>") you protect against the possibility that you get one of the protections wrong and also against future browser vulnerabilities. First, use an XSRF token as discussed earlier to make sure that JSON results containing confidential data are only returned to your own pages. Second, your JSON response pages should only support <CODE>POST</CODE> requests, which prevents the script from being loaded via a script tag. Third, you should make sure that the script is not executable. The standard way of doing this is to append some non-executable prefix to it, like <CODE>])}while(1);</x></CODE>. A script running in the same domain can read the contents of the response and strip out the prefix, but scripts running in other domains can't. </P> <P> NOTE: Making the script not executable is more subtle than it seems. It's possible that what makes a script executable may change in the future if new scripting features or languages are introduced. Some people suggest that you can protect the script by making it a comment by surrounding it with <CODE>/*</CODE> and <CODE>*/</CODE>, but that's not as simple as it might seem. (Hint: what if someone included <CODE>*/</CODE> in one of their snippets?) </P> <P> There's <A href="https://www.google.com/search?q=%22cross+site+script+inclusion%22" target="_top">much more to XSSI</A> than this. There's a variation of JSON called JSONP which you should avoid using because it allows script injection <I>by design</I>. And there's <A href="https://www.google.com/search?q=E4X+markup+security" target="_top">E4X</A> (Ecmascript for XML) which can result in your HTML file being parsed as a script. Surprisingly, one way to protect against E4X attacks is to put some invalid XML in your files, like the <CODE></x></CODE> above. </P> <P></P> </DIV> </DIV> <BR><P></P> <FONT SIZE="+2"> <A href="/part4">Continue >></A> </FONT><BR> <BR> <P style="font-size:x-small"> © Google 2017 <A href="https://www.google.com/intl/en/policies/terms/">Terms of Service</A> <BR>The code portions of this codelab are licensed under the Creative Commons Attribution-No Derivative Works 3.0 United States license <<A href="https://creativecommons.org/licenses/by-nd/3.0/us/">https://creativecommons.org/licenses/by-nd/3.0/us</A>>. Brief excerpts of the code may be used for educational or instructional purposes provided this notice is kept intact. Except as otherwise noted the remainder of this codelab is licensed under the Creative Commons Attribution 3.0 United States license <<A href="https://creativecommons.org/licenses/by/3.0/us/">https://creativecommons.org/licenses/by/3.0/us</A>>. </P> </BODY></HTML> |
| URL | http://google-gruyere.appspot.com/part4 |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 363 bytes. |
GET http://google-gruyere.appspot.com/part4 HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/ Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 244 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: c1a0138a874d01326b102c3fecac557e Date: Tue, 06 Feb 2024 17:42:21 GMT Server: Google Frontend Content-Length: 24149 |
| Response Body - size: 24,149 bytes. |
<HTML><HEAD><META http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<TITLE>Web Application Exploits and Defenses</TITLE> <LINK type="text/css" rel="stylesheet" href="../static/codelab.css"> <!--MARK-Z--> <SCRIPT> function toggleBlock(heading, whichID) { var image = heading.childNodes[0]; var block = document.getElementById(whichID); if (block) { if (getDisplay(block) == 'block') { block.style.display = 'none'; image.src = 'static/closed.gif'; } else { // "none" or "" block.style.display = 'block'; image.src = 'static/open.gif'; } } } function getDisplay(block) { var value = block.style.display; if (!value) { if (document.defaultView) { var computedStyle = document.defaultView.getComputedStyle(block, ""); value = computedStyle.getPropertyValue('display'); } else if (block.currentStyle) { value = block.currentStyle.display; } } return value; } </SCRIPT> </HEAD> <BODY bgcolor="#ffffff"> <DIV class="banner"></DIV> <H1><FONT size="+2"><IMG src="/static/gruyere-78.png" style="vertical-align:middle"> Web Application Exploits and Defenses (Part 4)</FONT> </H1> <P> A Codelab by Bruce Leban, Mugdha Bendre, and Parisa Tabriz </P> <DIV class="printable"> <DIV class="column1"> Table of Contents <UL> <LI class="L1" style="padding-top:0pt"> <A href="/#0__hackers">Beat the hackers</A></LI> <LI class="L1" style="padding-top:0pt"> <A href="/#0__gruyere">Gruyere</A></LI> <LI class="L1"> <A href="/part1#1__setup">Set-up</A> <UL> <LI class="L2"> <A href="/part1#1__reset_button">Reset Button</A></LI> <LI class="L2"> <A href="/part1#1__about_the_code">About the Code</A></LI> <LI class="L2"> <A href="/part1#1__features_and_technologies">Features and Technologies</A></LI> </UL></LI> <LI class="L1"> <A href="/part1#1__using_gruyere">Using Gruyere</A></LI> <LI class="L1"> <A href="/part2#2__cross_site_scripting">Cross-Site Scripting (XSS)</A> <UL> <LI class="L2"> <A href="/part2#2__xss_challenge">XSS Challenges</A></LI> <LI class="L2"> <A href="/part2#2__file_upload_xss">File Upload XSS</A></LI> <LI class="L2"> <A href="/part2#2__reflected_xss">Reflected XSS</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss">Stored XSS</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss_via_html_attribute">Stored XSS via HTML Attribute</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss_via_ajax">Stored XSS via AJAX</A></LI> <LI class="L2"> <A href="/part2#2__reflected_xss_via_ajax">Reflected XSS via AJAX</A></LI> <LI class="L2"> <A href="/part2#2__more_about_xss">More about XSS</A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__client_state_manipulation">Client-State Manipulation</A> <UL> <LI class="L2"> <A href="/part3#3__elevation_of_privilege"> Elevation of Privilege </A></LI> <LI class="L2"> <A href="/part3#3__cookie_manipulation"> Cookie Manipulation </A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__cross_site_request_forgery">Cross-Site Request Forgery (XSRF)</A> <UL> <LI class="L2"> <A href="/part3#3__xsrf_challenge">XSRF Challenge</A></LI> <LI class="L2"> <A href="/part3#3__more_about_preventing_xsrf">More about preventing XSRF</A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__cross_site_script_inclusion">Cross Site Script Inclusion (XSSI)</A> <UL> <LI class="L2"> <A href="/part3#3__xssi_challenge">XSSI Challenge</A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__path_traversal">Path Traversal</A> <UL> <LI class="L2"> <A href="/part4#4__information_disclosure_path_traversal">Information disclosure via path traversal </A></LI> <LI class="L2"> <A href="/part4#4__data_tampering_path_traversal">Data tampering via path traversal </A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__denial_of_service">Denial of Service</A> <UL> <LI class="L2"> <A href="/part4#4__dos_quit_server">DoS - Quit the Server</A></LI> <LI class="L2"> <A href="/part4#4__dos_overload_server">DoS - Overloading the Server</A></LI> <LI class="L2"> <A href="/part4#4__more_dos">More on Denial of Service</A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__code_execution">Code Execution</A> <UL> <LI class="L2"> <A href="/part4#4__code_execution_challenge">Code Execution Challenge</A></LI> <LI class="L2"> <A href="/part4#4__more_code_execution">More on Remote Code Execution</A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__configuration_vulnerabilities">Configuration Vulnerabilities</A> <UL> <LI class="L2"> <A href="/part5#5__information_disclosure_config_1">Information disclosure #1</A></LI> <LI class="L2"> <A href="/part5#5__information_disclosure_config_2">Information disclosure #2</A></LI> <LI class="L2"> <A href="/part5#5__information_disclosure_bug_3">Information disclosure #3 </A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__ajax_vulnerabilities">AJAX vulnerabilities</A> <UL> <LI class="L2"> <A href="/part5#5__dos_via_ajax">DoS via AJAX</A></LI> <LI class="L2"> <A href="/part5#5__phishing_via_ajax">Phishing via AJAX</A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__other_vulnerabilities"> Other Vulnerabilities </A> </H2> <UL> <LI class="L2"> <A href="/part5#5__buffer_and_integer_overflow">Buffer Overflow and Integer Overflow</A></LI> <LI class="L2"> <A href="/part5#5__sql_injection"> SQL Injection </A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__after_the_codelab">After the Codelab</A></LI> </UL> </DIV> <DIV class="column2"> <P></P> <P></P> <P> <NOAUTOLINK> </NOAUTOLINK></P> <H2><A name="4__path_traversal"> </A> Path Traversal </H2> <P> Most web applications serve static resources like images and CSS files. Frequently, applications simply serve all the files in a folder. If the application isn't careful, the user can use a path traversal attack to read files from other folders that they shouldn't have access to. For example, in both Windows and Linux, <CODE>..</CODE> represents the parent directory, so if you can inject <CODE>../</CODE> in a path you can "escape" to the parent directory. </P> <P> If an attacker knows the structure of your file system, then they can craft a URL that will traverse out of the installation directory to <CODE>/etc</CODE>. For example, if Picasa was vulnerable to path traversal (it isn't) and the Picasa servers use a Unix-like system, then the following would retrieve the password file: </P> <P></P> <PRE> https://www.picasa.com/../../../../../../../etc/passwd </PRE> <P></P> <P></P> <H3><A name="4__information_disclosure_path_traversal"> </A> Information disclosure via path traversal </H3> <P> <IMG src="/static/cheese_bw.png" style="vertical-align:middle; padding-right: 5px;"> <B>Find a way to read <CODE>secret.txt</CODE> from a running Gruyere server.</B> </P> <P> Amazingly, this attack is not even necessary in many cases: people often install applications and never change the defaults. So the first thing an attacker would try is the default value. </P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'read_secret_txt_hint');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="read_secret_txt_hint" style="display:none"> <P> This isn't a black box attack because you need to know that the <CODE>secret.txt</CODE> file exists, where it's stored, and where Gruyere stores its resource files. You don't need to look at any source code. </P> <P></P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'read_secret_txt_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="read_secret_txt_hint2" style="display:none"> <P> How does the server know which URLs represent resource files? You can use curl or a web proxy to craft request URLs that some browsers may not allow. </P> <P></P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'read_secret_txt_sol');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="read_secret_txt_sol" style="display:none"> <P> <B>To exploit,</B> you can steal <CODE>secret.txt</CODE> via this URL: </P> <P></P> <PRE> https://google-gruyere.appspot.com/382665580745386307547168512335551204731/../secret.txt </PRE> <P> Some browsers, like Firefox and Chrome, optimize out <CODE>../</CODE> in URLs. This doesn't provide any security protection because an attacker will use <CODE>%2f</CODE> to represent <CODE>/</CODE> in the URL; or a tool like curl, a web proxy or a browser that doesn't do that optimization. But if you test your application with one of these browsers to see if you're vulnerable, you might think you were protected when you're not. </P> <P> <B>To fix,</B> we need to prevent access to files outside the resources directory. Validating file paths is a bit tricky as there are various ways to hide path elements like "../" or "~" that allow escaping out of the resources folder. The best protection is to only serve specific resource files. You can either hardcode a list or when your application starts, you can crawl the resource directory and build a list of files. Then only accept requests for those files. You can even do some optimization here like caching small files in memory which will make your application faster. If you are going to try to file path validation, you need to do it on the final path, not on the URL, as there are numerous ways to represent the same characters in URLs. <I>Note: Changing file permissions will NOT work. Gruyere has to be able to read this file.</I> </P> <P></P> </DIV> </DIV> <P></P> <H3><A name="4__data_tampering_path_traversal"> </A> Data tampering via path traversal </H3> <P> <IMG src="/static/cheese_bw.png" style="vertical-align:middle; padding-right: 5px;"> <B>Find a way to replace <CODE>secret.txt</CODE> on a running Gruyere server.</B> </P> <P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'write_secret_txt_hint');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="write_secret_txt_hint" style="display:none"> <P> Again, this isn't a black box attack because you need to know about the directory structure that Gruyere uses, specifically where uploaded files are stored. </P> <P></P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'write_secret_txt_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="write_secret_txt_hint2" style="display:none"> <P> If I log in as user <CODE>brie</CODE> and upload a file, where does the server store it? Can you trick the server into uploading a file to <CODE>../../secret.txt</CODE>? </P> <P></P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'write_secret_txt_sol');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="write_secret_txt_sol" style="display:none"> <P> <B>To exploit,</B> create a new user named <CODE>..</CODE> and upload your new <CODE>secret.txt</CODE>. You could also create a user named <CODE>brie/../..</CODE>. </P> <P> <B>To fix,</B> you should escape dangerous characters in the username (replacing them with safe characters) before using it. It was earlier suggested that we should restrict the characters allowed in a username, but it probably didn't occur to you that <CODE>"."</CODE> was a dangerous character. It's worth noting that there's a vulnerability unique to Windows servers with this implementation. On Windows, filenames are not case sensitive but Gruyere usernames are. So one user can attack another user's files by creating a similar username that differs only in case, e.g., <CODE>BRIE</CODE> instead of <CODE>brie</CODE>. So we need to not just escape unsafe characters but convert the username to a canonical form that is different for different usernames. Or we could avoid all these issues by assigning each user a unique identifier instead. </P> <P> <B>Oops!</B> This doesn't completely solve the problem. Even with the above fix in place, there is another way to perform this attack. Can you find it? <H4 style="cursor: pointer" onclick="toggleBlock(this, 'another_write_secret_hint');"><IMG src="/static/closed.gif"> Hint </H4> <DIV id="another_write_secret_hint" style="display:none"> <P> Are there any limits on the filename when you do an upload? You may need to use a special tool like <CODE>curl</CODE> or a web proxy to perform this attack. </P> <P></P> </DIV> </P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'write_secret_txt_sol2');"><IMG src="/static/closed.gif"> Another Exploit and Fix </H4> <DIV id="write_secret_txt_sol2" style="display:none"> <P> Surprisingly, you can upload a file named <CODE>../secret.txt</CODE>. Gruyere provides no protection against this attack. Most browsers won't let you upload that file but, again, you can do it with curl or other tools. You need the same kind of protection when writing files as you do on read. </P> <P> As a general rule, you should never store user data in the same place as your application files but that alone won't protect against these attacks since if the user can inject <CODE>../</CODE> into the file path, they can traverse all the way to the root of the file system and then back down to the normal install location of your application (or even the Python interpreter itself). </P></DIV></DIV> </DIV> <BR><P></P> <H2><A name="4__denial_of_service"> </A> Denial of Service </H2> <P> A denial of service (DoS) attack is an attempt to make a server unable to service ordinary requests. A common form of DoS attack is sending more requests to a server than it can handle. The server spends all its time servicing the attacker's requests that it has very little time to service legitimate requests. Protecting an application against these kinds of DoS attacks is outside the scope of this codelab. And attacking Gruyere in this way would be interpreted as an attack on App Engine. </P><P> Hackers can also prevent a server from servicing requests by taking advantage of server bugs, such as sending requests that crash a server, make it run out of memory, or otherwise cause it fail serving legitimate requests in some way. In the next few challenges, you'll take advantage of bugs in Gruyere to perform DoS attacks. </P> <H3><A name="4__dos_quit_server"> </A> DoS - Quit the Server </H3> The simplest form of denial of service is shutting down a service. <P> <IMG src="/static/cheese_bw.png" style="vertical-align:middle; padding-right: 5px;"> <B>Find a way to make the server quit.</B> </P> <P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'dos_quit_hint');"><IMG src="/static/closed.gif"> Hint </H4> <DIV id="dos_quit_hint" style="display:none"> <P> How does an administrator make the server quit? The server management page is <CODE><A href="/code/?resources/manage.gtl">manage.gtl</A></CODE>. </P> <P></P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'dos_quit_soln');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="dos_quit_soln" style="display:none"> <P> <B>To exploit,</B> make a request to <CODE>https://google-gruyere.appspot.com/382665580745386307547168512335551204731/quitserver</CODE>. You should need to be logged in as an administrator to do this, but you don't. </P><P> This is another example of a common bug. The server protects against non-administrators accessing certain URLs but the list includes <CODE>/quit</CODE> instead of the actual URL <CODE>/quitserver</CODE>. </P> <P> <B>To fix,</B> add <CODE>/quitserver</CODE> to the URLS only accessible to administrators: </P><PRE> _PROTECTED_URLS = [ "/quitserver", "/reset" ] </PRE> <P> <!--MARK-A--> <B>Oops!</B> This doesn't completely solve the problem. The <CODE>reset</CODE> URL is in the protected list. Can you figure out how to access it? </P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'dos_bonus_hint');"><IMG src="/static/closed.gif"> Hint </H4> <DIV id="dos_bonus_hint" style="display:none"> <P> Look carefully at the code that handles URLs and checks for protected ones. </P></DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'dos_bonus_soln');"><IMG src="/static/closed.gif"> Another Exploit and Fix </H4> <DIV id="dos_bonus_soln" style="display:none"> <P> <B>To exploit,</B> use <CODE>https://google-gruyere.appspot.com/382665580745386307547168512335551204731/RESET</CODE>. The check for protected urls is case sensitive. After doing that check, it capitalizes the string to look up the implementation. This is a classic check/use bug where the condition being checked does not match the actual use. This vulnerability is worse than the previous one because it exposes all the protected urls. </P><P> <B>To fix,</B> put the security check inside the dangerous functions rather than outside them. That ensures that no matter how we get there, the security check can't be skipped. </P></DIV> </DIV> </DIV> <P> <H3><A name="4__dos_overload_server"></A> DoS - Overloading the Server </H3> <P> <IMG src="/static/cheese_b.png" style="vertical-align:middle; padding-right: 5px;"> <B>Find a way to overload the server when it processes a request.</B> </P> <P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'dos_overload_hint1');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="dos_overload_hint1" style="display:none"> <P> You can upload a template that does this. </P></DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'dos_overload_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="dos_overload_hint2" style="display:none"> <P> Every page includes the <CODE><A href="/code/?resources/menubar.gtl">menubar.gtl</A></CODE> template. Can you figure out how to make that template overload the server? </P></DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'dos_overload_soln');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="dos_overload_soln" style="display:none"> <P> <B>To exploit,</B> create a file named <CODE>menubar.gtl</CODE> containing: <PRE> [[include:menubar.gtl]]DoS[[/include:menubar.gtl]] </PRE> and upload it to the <CODE>resources</CODE> directory using a path traversal attack, e.g., creating a user named <CODE>../resources</CODE>. </P> <P> <B>To fix,</B> implement the protections against path traversal and uploading templates discussed earlier. </P> </DIV> <B>NOTE:</B> After performing the previous exploit, you'll need to push the <A href="/part1#1__reset_button">reset button</A>. </P> </DIV> <H3><A name="4__more_dos"> </A> More on Denial of Service </H3> <P> Unlike a well defined vulnerability like XSS or XSRF, denial of service describes a wide class of attacks. This might mean bringing your service down or flooding your inbox so you can't receive legitimate mail. Some things to consider: </P> <P></P> <UL> <LI><P> If you were evil and greedy, how quickly could you take down your application or starve all of its resources? For example, is it possible for a user to upload their hard drive to your application? Entering the attacker's mindset can help identify DoS points in your application. Additionally, think about where the computationally and memory intensive tasks are in your application and put safeguards in place. Do sanity checks on input values. </P> <LI><P>Put monitoring in place so you can detect when you are under attack and enforce per user quotas and rate limiting to ensure that a small subset of users cannot starve the rest. Abusive patterns could include increased memory usage, higher latency, or more requests or connections than usual. </P> </UL> <!--MARK-B--> <BR><P></P> <H2><A name="4__code_execution"> </A> Code Execution </H2> <P> If an attacker can execute arbitrary code remotely on your server, it's usually game over. They may be able to take control over the running program or potentially break out the process to open a new shell on the computer. From here, it's usually not hard to compromise the entire machine the server is running on. </P> <P> Similar to information disclosure and denial of service, there is no recipe or specific defense to prevent remote code execution. The program must perform validation of all user input before handling it and where possible, implement functions with least privilege rights. This topic can't be done justice in just a short paragraph, but know that this is likely the scariest results a security bug can have and trumps any of the above attacks. </P> <P></P> <H3><A name="4__code_execution_challenge"> </A> Code Execution Challenge </H3> <P> <IMG src="/static/cheese_w.png" style="vertical-align:middle; padding-right: 5px;"> <B>Find a code execution exploit.</B> </P> <P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'code_upload_hint');"><IMG src="/static/closed.gif"> Hint </H4> <DIV id="code_upload_hint" style="display:none"> <P> You need to use two previous exploits. </P></DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'code_upload_soln');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="code_upload_soln" style="display:none"> <P> <B>To exploit,</B> make a copy of <CODE><A href="/code/?gtl.py">gtl.py</A></CODE> (or <CODE><A href="/code/?sanitize.py">sanitize.py</A></CODE>) and add some exploit code. Now you can either upload a file named <CODE>../gtl.py</CODE> or create a user named <CODE>..</CODE> and upload <CODE>gtl.py</CODE>. Then, make the server quit by browsing to <CODE>https://google-gruyere.appspot.com/382665580745386307547168512335551204731/quitserver</CODE>. When the server restarts, your code will run. </P> <P> This attack was possible because Gruyere has permission to both read and write files in the Gruyere directory. Applications should run with the minimal privileges possible. </P> <P> Why would you attack <CODE>gtl.py</CODE> or <CODE>sanitize.py</CODE> rather than <CODE>gruyere.py</CODE>? When an attacker has a choice, they would usually choose to attack the infrastructure rather than the application itself. The infrastructure is less likely to be updated and less likely to be noticed. When was the last time you checked that no one had replaced <CODE>python.exe</CODE> with a trojan? </P> <P> <B>To fix,</B> fix the two previous exploits. </P> <P></P> </DIV> </DIV> <P></P> <H3><A name="4__more_code_execution"> </A> More on Remote Code Execution </H3> <P> Even though there is no single or simple defense to remote code execution, here is a short list of some preventative measures: </P> <UL> <LI><P><B>Least Privilege:</B> Always run your application with the <A href="https://www.google.com/search?q=least+privileges" target="_top">least privileges</A> it needs. </P> <LI><P><B>Application Level Checks:</B> Avoid passing user input directly into commands that evaluate arbitrary code, like <CODE>eval()</CODE> or <CODE>system()</CODE>. Instead, use the user input as a switch to choose from a set of developer controlled commands. </P> <LI><P><B>Bounds Checks:</B> Implement proper bounds checks for non-safe languages like C++. Avoid <A href="https://www.google.com/search?q=unsafe+string+functions" target="_top">unsafe string functions</A>. Keep in mind that even safe languages like Python and Java use native libraries. </P> </UL> <!--MARK-C--> <BR><P></P> <FONT SIZE="+2"> <A href="/part5">Continue >></A> </FONT><BR> <BR> <P style="font-size:x-small"> © Google 2017 <A href="https://www.google.com/intl/en/policies/terms/">Terms of Service</A> <BR>The code portions of this codelab are licensed under the Creative Commons Attribution-No Derivative Works 3.0 United States license <<A href="https://creativecommons.org/licenses/by-nd/3.0/us/">https://creativecommons.org/licenses/by-nd/3.0/us</A>>. Brief excerpts of the code may be used for educational or instructional purposes provided this notice is kept intact. Except as otherwise noted the remainder of this codelab is licensed under the Creative Commons Attribution 3.0 United States license <<A href="https://creativecommons.org/licenses/by/3.0/us/">https://creativecommons.org/licenses/by/3.0/us</A>>. </P> </BODY></HTML> |
| URL | http://google-gruyere.appspot.com/part5 |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 363 bytes. |
GET http://google-gruyere.appspot.com/part5 HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/ Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 244 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: 0734f8d6045b2c21582855d246b3c640 Date: Tue, 06 Feb 2024 17:42:21 GMT Server: Google Frontend Content-Length: 25303 |
| Response Body - size: 25,303 bytes. |
<HTML><HEAD><META http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<TITLE>Web Application Exploits and Defenses</TITLE> <LINK type="text/css" rel="stylesheet" href="../static/codelab.css"> <!--MARK-Z--> <SCRIPT> function toggleBlock(heading, whichID) { var image = heading.childNodes[0]; var block = document.getElementById(whichID); if (block) { if (getDisplay(block) == 'block') { block.style.display = 'none'; image.src = 'static/closed.gif'; } else { // "none" or "" block.style.display = 'block'; image.src = 'static/open.gif'; } } } function getDisplay(block) { var value = block.style.display; if (!value) { if (document.defaultView) { var computedStyle = document.defaultView.getComputedStyle(block, ""); value = computedStyle.getPropertyValue('display'); } else if (block.currentStyle) { value = block.currentStyle.display; } } return value; } </SCRIPT> </HEAD> <BODY bgcolor="#ffffff"> <DIV class="banner"></DIV> <H1><FONT size="+2"><IMG src="/static/gruyere-78.png" style="vertical-align:middle"> Web Application Exploits and Defenses (Part 5)</FONT> </H1> <P> A Codelab by Bruce Leban, Mugdha Bendre, and Parisa Tabriz </P> <DIV class="printable"> <DIV class="column1"> Table of Contents <UL> <LI class="L1" style="padding-top:0pt"> <A href="/#0__hackers">Beat the hackers</A></LI> <LI class="L1" style="padding-top:0pt"> <A href="/#0__gruyere">Gruyere</A></LI> <LI class="L1"> <A href="/part1#1__setup">Set-up</A> <UL> <LI class="L2"> <A href="/part1#1__reset_button">Reset Button</A></LI> <LI class="L2"> <A href="/part1#1__about_the_code">About the Code</A></LI> <LI class="L2"> <A href="/part1#1__features_and_technologies">Features and Technologies</A></LI> </UL></LI> <LI class="L1"> <A href="/part1#1__using_gruyere">Using Gruyere</A></LI> <LI class="L1"> <A href="/part2#2__cross_site_scripting">Cross-Site Scripting (XSS)</A> <UL> <LI class="L2"> <A href="/part2#2__xss_challenge">XSS Challenges</A></LI> <LI class="L2"> <A href="/part2#2__file_upload_xss">File Upload XSS</A></LI> <LI class="L2"> <A href="/part2#2__reflected_xss">Reflected XSS</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss">Stored XSS</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss_via_html_attribute">Stored XSS via HTML Attribute</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss_via_ajax">Stored XSS via AJAX</A></LI> <LI class="L2"> <A href="/part2#2__reflected_xss_via_ajax">Reflected XSS via AJAX</A></LI> <LI class="L2"> <A href="/part2#2__more_about_xss">More about XSS</A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__client_state_manipulation">Client-State Manipulation</A> <UL> <LI class="L2"> <A href="/part3#3__elevation_of_privilege"> Elevation of Privilege </A></LI> <LI class="L2"> <A href="/part3#3__cookie_manipulation"> Cookie Manipulation </A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__cross_site_request_forgery">Cross-Site Request Forgery (XSRF)</A> <UL> <LI class="L2"> <A href="/part3#3__xsrf_challenge">XSRF Challenge</A></LI> <LI class="L2"> <A href="/part3#3__more_about_preventing_xsrf">More about preventing XSRF</A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__cross_site_script_inclusion">Cross Site Script Inclusion (XSSI)</A> <UL> <LI class="L2"> <A href="/part3#3__xssi_challenge">XSSI Challenge</A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__path_traversal">Path Traversal</A> <UL> <LI class="L2"> <A href="/part4#4__information_disclosure_path_traversal">Information disclosure via path traversal </A></LI> <LI class="L2"> <A href="/part4#4__data_tampering_path_traversal">Data tampering via path traversal </A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__denial_of_service">Denial of Service</A> <UL> <LI class="L2"> <A href="/part4#4__dos_quit_server">DoS - Quit the Server</A></LI> <LI class="L2"> <A href="/part4#4__dos_overload_server">DoS - Overloading the Server</A></LI> <LI class="L2"> <A href="/part4#4__more_dos">More on Denial of Service</A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__code_execution">Code Execution</A> <UL> <LI class="L2"> <A href="/part4#4__code_execution_challenge">Code Execution Challenge</A></LI> <LI class="L2"> <A href="/part4#4__more_code_execution">More on Remote Code Execution</A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__configuration_vulnerabilities">Configuration Vulnerabilities</A> <UL> <LI class="L2"> <A href="/part5#5__information_disclosure_config_1">Information disclosure #1</A></LI> <LI class="L2"> <A href="/part5#5__information_disclosure_config_2">Information disclosure #2</A></LI> <LI class="L2"> <A href="/part5#5__information_disclosure_bug_3">Information disclosure #3 </A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__ajax_vulnerabilities">AJAX vulnerabilities</A> <UL> <LI class="L2"> <A href="/part5#5__dos_via_ajax">DoS via AJAX</A></LI> <LI class="L2"> <A href="/part5#5__phishing_via_ajax">Phishing via AJAX</A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__other_vulnerabilities"> Other Vulnerabilities </A> </H2> <UL> <LI class="L2"> <A href="/part5#5__buffer_and_integer_overflow">Buffer Overflow and Integer Overflow</A></LI> <LI class="L2"> <A href="/part5#5__sql_injection"> SQL Injection </A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__after_the_codelab">After the Codelab</A></LI> </UL> </DIV> <DIV class="column2"> <P></P> <P></P> <P> <NOAUTOLINK> </NOAUTOLINK></P> <H2><A name="5__configuration_vulnerabilities"> </A> Configuration Vulnerabilities </H2> <P> Applications are often installed with default settings that attackers can use to attack them. This is particularly an issue with third party software where an attacker has easy access to a copy of the same application or framework you are running. Hackers know the default account names and passwords. For example, looking at the contents of <CODE><A href="/code/?data.py">data.py</A></CODE> you know that there's a default administrator account named 'admin' with the password 'secret'. </P> <P> Configuration vulnerabilities also include features that increase attack surface. A common example is a feature that is on by default but you are not using, so you didn't configure it and the default configuration is vulnerable. It also includes debug features like status pages or dumping stack traces on failures. </P> <P></P> <H3><A name="5__information_disclosure_config_1"> </A> Information disclosure #1 </H3> <P> <IMG src="/static/cheese_w.png" style="vertical-align:middle; padding-right: 5px;"> <B>Read the contents of the database off of a running server by exploiting a configuration vulnerability.</B> </P> <P>You should look through the Gruyere code looking for default configurations or debug features that don't belong there.</P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'disclosure_1_hint');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="disclosure_1_hint" style="display:none"> <P> Look at all the files installed with Gruyere. Are there any files that shouldn't be there? </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'disclosure_1_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="disclosure_1_hint2" style="display:none"> <P> Look for a <CODE>.gtl</CODE> file that isn't referenced anywhere. </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'disclosure_1_sol');"><IMG src="/static/closed.gif"> Exploit and Fixes </H4> <DIV id="disclosure_1_sol" style="display:none"> <P> <B>To exploit,</B> you can use the debug dump page <CODE><A href="/code/?resoources/dump.gtl">dump.gtl</A></CODE> to display the contents of the database via the following URL: </P><PRE> https://google-gruyere.appspot.com/382665580745386307547168512335551204731/dump.gtl </PRE> <P> <B>To fix,</B> always make sure debug features are not installed. In this case, delete <CODE>dump.gtl</CODE>. This is an example of the kind of debug feature that might be left in an application by mistake. If a debug feature like this is necessary, then it needs to be carefully locked down: only admin users should have access and only requests from debug IP addresses should be accepted. </P><P> This exploit exposes the users' passwords. Passwords should never be stored in cleartext. Instead, you should use <A href="https://www.google.com/search?q=password+hashing">password hashing</A>. The idea is that to authenticate a user, you don't need to know their password, only be convinced that the user knows it. When the user sets their password, you store only a cryptographic hash of the password and a salt value. When the user re-enters their password later, you recompute the hash and if it matches you conclude the password is correct. If an attacker obtains the hash value, it's very difficult for them to reverse that to find the original password. (Which is a good thing, since despite <A href="https://www.google.com/search?q=choosing+a+good+password">lots of advice</A> to the contrary, users frequently use the same weak passwords for multiple sites.) </P></DIV> </DIV> <P></P> <H3><A name="5__information_disclosure_config_2"> </A> Information disclosure #2 </H3> <P> <IMG src="/static/cheese_w.png" style="vertical-align:middle; padding-right: 5px;"> <B>Even after implementing the fix described above, an attacker can undo it and execute the attack! How can that be?</B> </P> <P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'disclosure_2_hint');"><IMG src="/static/closed.gif"> Hint </H4> <DIV id="disclosure_2_hint" style="display:none"> <P> You can upload a file of any type. </P> <P></P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'disclosure_2_sol');"><IMG src="/static/closed.gif"> Exploit and Fixes </H4> <DIV id="disclosure_2_sol" style="display:none"> <P> <B>To exploit,</B> Gruyere allows the user to upload files of any type, including <CODE>.gtl</CODE> files. So the attacker can simply upload their own copy of <CODE><A href="/code/?resources/dump.gtl">dump.gtl</A></CODE> or a similar file and than access it. In fact, as noted earlier, hosting arbitrary content on the server is a major security risk whether it's HTML, JavaScript, Flash or something else. Allowing a file with an unknown file type may lead to a security hole in the future. </P> <P> <B>To fix,</B> we should do several things: </P><OL> <LI> Only files that are part of Gruyere should be treated as templates. </LI> <LI> Don't store user uploaded files in the same place as application files. </LI> <LI> Consider limiting the types of files that can be uploaded (via a whitelist). </LI> </OL> </DIV> </DIV> <H3><A name="5__information_disclosure_bug_3"> </A> Information disclosure #3 </H3> <P> <IMG src="/static/cheese_w.png" style="vertical-align:middle; padding-right: 5px;"> <B>Even after implementing the fixes described above, a similar attack is still possible through a different attack vector. Can you find it?</B> </P> <P> This attack isn't a a configuration vulnerability, just bad code. </P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'disclosure_3_hint1');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="disclosure_3_hint1" style="display:none"> <P>You can insert something in your private snippet which will display the contents of the database.</P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'disclosure_3_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="disclosure_3_hint2" style="display:none"> <P>This attack is closely related to the previous ones. There is a bug in the code that expands templates that you can exploit.</P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'disclosure_3_sol');"><IMG src="/static/closed.gif"> Exploit and Fixes </H4> <DIV id="disclosure_3_sol" style="display:none"> <P> There is a defect in Gruyere's template expansion code that reparses expanded variables. Specifically, when expanding a block it expands variables in the block. Then it parses the block as a template and expands variables again, </P> <B>To exploit,</B> add this to your private snippet: <PRE> {{_db:pprint}} </PRE> </P> <P> <B>To fix,</B> modify the template code so it never reparses inserted variable values. The defect in the code is due to the fact that <CODE>ExpandTemplate</CODE> calls <CODE>_ExpandBlocks</CODE> followed by <CODE>_ExpandVariables</CODE>, but <CODE>_ExpandBlocks</CODE> calls <CODE>ExpandTemplate</CODE> on nested blocks. So if a variable is expanded inside a nested block and contains something that looks like a variable template, it will get expanded a second time. That sounds complicated because it is complicated. Parsing blocks and variables separately is a fundamental flaw in the design of the expander, so the fix is non-trivial. </P> <P> This exploit is possible because the template language allows arbitrary database access. It would be safer if the templates were only allowed to access data specifically provided to them. For example, a template could have an associated database query and only the data matched by that query would be passed to the template. This would limit the scope of a bug like this to data that the user was already allowed to access. </P> </DIV> </DIV> <BR><BR> </P><H2><A name="5__ajax_vulnerabilities"> </A> AJAX vulnerabilities </H2> Bad AJAX code allows attackers to modify parts of your application in ways that you might not expect. In traditional client development, there is a clear separation between the application and the data it displays. That's not true in web applications as the next two attacks will make clear. <P></P> <H3><A name="5__dos_via_ajax"> </A> DoS via AJAX </H3> <P> <IMG src="/static/cheese_b.png" style="vertical-align:middle; padding-right: 5px;"> <B>Find an attack that prevents users from seeing their private snippets on the home page.</B> (The attack should be triggered after clicking the refresh link and without using XSS.) </P> <P></P> <DIV style="margin-left:18pt"> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'dom_hint');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="dom_hint" style="display:none"> <P> Can you figure out how to change the value of the private snippet in the AJAX response? </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'dom_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="dom_hint2" style="display:none"> <P> What happens if a JSON object has a duplicate key value? </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'dom_sol');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="dom_sol" style="display:none"> <P> <B>To exploit,</B> create a user named <CODE>private_snippet</CODE> and create at least one snippet. The JSON response will then be <CODE>{'private_snippet' : <user's private snippet>, ..., 'private_snippet' : <attacker's snippet>}</CODE> and the attacker's snippet replaces the user's. </P> <P> <B>To fix,</B> the AJAX code needs to make sure that the data only goes where it's supposed to go. The flaw here is that the JSON structure is not robust. A better structure would be <CODE>[<private_snippet>, {<user> : <snippet>,...}]</CODE>. </P> <P></P> </DIV> </DIV> <P></P> <H3><A name="5__phishing_via_ajax"> </A> Phishing via AJAX </H3> <P> While the previous attack may seem like a minor inconvenience, careless DOM manipulation can lead to much more serious problems. </P> <P> <IMG src="/static/cheese_b.png" style="vertical-align:middle; padding-right: 5px;"> <B>Find a way to change the sign in link in the upper right corner to point to <CODE>https://evil.example.com</CODE>.</B> </P> <P> (The attack should be triggered after clicking the refresh link and without using XSS or a script.) </P> <P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'phishing_hint');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="phishing_hint" style="display:none"> <P> Look at what the script does to replace the snippets on the page. Can you get it to replace the sign in link? </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'phishing_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="phishing_hint2" style="display:none"> <P> Look at the AJAX code to see how it replaces each snippet, and then look at the structure of the home page and see if you can see what else you might be able to replace. (You can't just replace the sign in link. You'll have to replace a bit more.) </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'phishing_sol');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="phishing_sol" style="display:none"> <P> <B>To exploit,</B> create a user named <CODE>menu-right</CODE> and publish a snippet that looks exactly like the right side of the menu bar. </P> <P></P> <PRE> <a href='https://evil.example.com/login'>Sign in</a> | <a href='https://evil.example.com/newaccount.gtl'>Sign up</a> </PRE> <P> If the user is already logged in, the menu bar will look wrong. But that's ok, since there's a good chance the user will just think they somehow accidentally got logged out of the web site and log in again. </P> <P> <B>To fix,</B> the process of modifying the DOM needs to be made more robust. When user values are used in as DOM element identifiers, you should ensure that there can't be a conflict as there is here, for example, applying a prefix to user values like <CODE>id="user_<USER>"</USER></CODE>. Even better, use your own identifiers rather than user values. </P> <P> This spoofing attack is easily detected when the user clicks Sign in and ends up at <CODE>evil.example.com</CODE>. A clever attacker could do something harder to detect, like replacing the Sign in link with a script that renders the sign in form on the current page with the form submission going to their server. </P> <P></P> </DIV> </DIV> <BR><P></P> <P></P> <H2><A name="5__other_vulnerabilities"> </A> Other Vulnerabilities </H2> <H3><A name="5__buffer_and_integer_overflow"> </A> Buffer Overflow and Integer Overflow </H3> <DIV><P>A <A href="https://www.google.com/search?q=buffer+overflow" target="_top">buffer overflow</A> vulnerability exists when an application does not properly guard its buffers and allow user data to write past the end of a buffer. This excess data can modify other variables, including pointers and function return addresses, leading to arbitrary code execution. Historically, buffer overflow vulnerabilities have been responsible for some of the most widespread internet attacks including <a href="https://www.google.com/search?q=sql+slammer">SQL Slammer<a>, <a href="https://www.google.com/search?q=blaster+worm">Blaster</a> and <a href="https://www.google.com/search?q=code+red+worm">Code Red</a> computer worms. The PS2, Xbox and Wii have all been hacked using buffer overflow exploits. </P> <P>While not as well known, <A href="https://www.google.com/search?q=integer+overflow+vulnerability" target="_top">integer overflow</A> vulnerabilities can be just as dangerous. Any time an integer computation silently returns an incorrect result, the application will operate incorrectly. In the best case, the application fails. In the worst case, there is a security bug. For example, if an application checks that <CODE>length + 1 < limit</CODE> then this will succeed if <CODE>length</CODE> is the largest positive integer value, which can then expose a buffer overflow vulnerability. </P> <P>This codelab doesn't cover overflow vulnerabilities because Gruyere is written in Python, and therefore not vulnerable to typical buffer and integer overflow problems. Python won't allow you to read or write outside the bounds of an array and integers can't overflow. While C and C++ programs are most commonly known to expose these vulnerabilities, other languages are not immune. For example, while Java was designed to prevent buffer overflows, it silently ignores integer overflow. </P> <P> Like all applications, Gruyere is vulnerable to platform vulnerabilities. That is, if there are security bugs in the platforms that Gruyere is built on top of, then those bugs would also apply to Gruyere. Gruyere's platform includes: the Python runtime system and libraries, AppEngine, the operating system that Gruyere runs on and the client side software (including the web browser) that users use to run Gruyere. While platform vulnerabilities are important, they are outside the scope of this codelab as you generally can't fix platform vulnerabilities by making changes to your application. Fixing platform vulnerabilities yourself is also not practical for many people, but you can mitigate your risks by making sure that you are diligent in applying security updates as they are released by platform vendors. </P> </DIV> <!--MARK-D--> <H3><A name="5__sql_injection"> </A> SQL Injection </H3> <DIV><P>Just as XSS vulnerabilities allow attackers to inject script into web pages, <a href="https://www.google.com/search?q=sql+injection">SQL injection</a> vulnerabilities allow attackers to inject arbitrary scripts into SQL queries. When a SQL query is executed it can either read or write data, so an attacker can use SQL injection to read your entire database as well as overwrite it, as described in the classic <a href="https://xkcd.com/327/">Bobby Tables</a> XKCD comic. If you use SQL, the most important advice is to avoid building queries by string concatenation: use API calls instead. This codelab doesn't cover SQL injection because Gruyere doesn't use SQL. </P> </DIV> <BR><BR> <H2><A name="5__after_the_codelab"> </A> After the Codelab </H2> <P> We hope that you found this codelab instructive. If you want more practice, there are many more security bugs in Gruyere than the ones described above. You should attack your own application using what you've learned and write unit tests to verify that these bugs are not present and won't get introduced in the future. You should also consider using <A href="https://www.google.com/search?q=fuzz+testing" target="_top">fuzz testing</A> tools. For more information about security at Google, please visit our <A href="https://security.googleblog.com/" target="_top">blog</A> or our <A href="https://www.google.com/about/appsecurity/" target="_top">corporate security page</A>. </P> <P> If you'd like to share this codelab with others, please consider tweeting or buzzing about it or posting one of these badges on your blog or personal page: <!--MARK-E--> </P> <div> <table style="border-collapse:collapse;border:solid 1 black" cellpadding="5"> <tr><td style="border:solid 1 black;text-align:center;vertical-align:middle"> <a href="https://google-gruyere.appspot.com/"> <img src="../static/gruyere-badge.png" style="padding:4pt" border="0" alt="Learn how to make web apps more secure. Do the Gruyere codelab."></a> </td><td style="border:solid 1 black;vertical-align:middle"> <pre style="margin:0"> <a href="https://google-gruyere.appspot.com/"> <img src="//google-gruyere.appspot.com/static/gruyere-badge.png" style="padding:4pt" border="0" alt="Learn how to make web apps more secure. Do the Gruyere codelab."></a> </pre> <div align="center" style="margin:0">(Line breaks above should be copied verbatim.)</div> </td></tr><tr><td style="border:solid 1 black;text-align:center;vertical-align:middle"> <a href="https://google-gruyere.appspot.com/"> <img src="../static/gruyere-40.png" style="padding:4pt" border="0" title="Learn how to make web apps more secure. Do the Gruyere codelab." alt="Learn how to make web apps more secure. Do the Gruyere codelab."></a> </td><td style="border:solid 1 black;vertical-align:middle"> <pre style="margin:0"> <a href="https://google-gruyere.appspot.com/"> <img src="//google-gruyere.appspot.com/static/gruyere-40.png" style="padding:4pt" border="0" title="Learn how to make web apps more secure. Do the Gruyere codelab." alt="Learn how to make web apps more secure. Do the Gruyere codelab."></a> </pre> <div align="center" style="margin:0">(Line breaks above should be copied verbatim.)</div> </td></tr></table></div> <!--MARK-F--> <!--NEXTLINK--> <BR> <P style="font-size:x-small"> © Google 2017 <A href="https://www.google.com/intl/en/policies/terms/">Terms of Service</A> <BR>The code portions of this codelab are licensed under the Creative Commons Attribution-No Derivative Works 3.0 United States license <<A href="https://creativecommons.org/licenses/by-nd/3.0/us/">https://creativecommons.org/licenses/by-nd/3.0/us</A>>. Brief excerpts of the code may be used for educational or instructional purposes provided this notice is kept intact. Except as otherwise noted the remainder of this codelab is licensed under the Creative Commons Attribution 3.0 United States license <<A href="https://creativecommons.org/licenses/by/3.0/us/">https://creativecommons.org/licenses/by/3.0/us</A>>. </P> </BODY></HTML> |
| URL | http://google-gruyere.appspot.com/start |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 313 bytes. |
GET http://google-gruyere.appspot.com/start HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/robots.txt |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 354 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-Type: text/html; charset=utf-8 Pragma: no-cache Set-Cookie: GRUYERE_ID=382665580745386307547168512335551204731; Path=/ X-Cloud-Trace-Context: cbdf509c9c2a3e252715ab2ea38a1140 Date: Tue, 06 Feb 2024 17:42:17 GMT Server: Google Frontend Content-Length: 681 Expires: Tue, 06 Feb 2024 17:42:17 GMT |
| Response Body - size: 681 bytes. |
<HTML>
<STYLE> body, th, td, form { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; } h1 { color: #dd0000; } </STYLE> <TITLE>Start Gruyere</TITLE> <BODY> <H1>Start Gruyere</H1> Your Gruyere instance id is 382665580745386307547168512335551204731. <br><br><span style="color:red"><b>WARNING: Gruyere is not secure.<br> Do not upload any personal or private data.</b></span> <br><br>By using Gruyere you agree to the <A href="https://www.google.com/intl/en/policies/terms/">terms of service</A>. <H2><A HREF="/382665580745386307547168512335551204731">Agree & Start</A></H2> </BODY></HTML> |
| URL | http://google-gruyere.appspot.com/static/codeindex.html |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 384 bytes. |
GET http://google-gruyere.appspot.com/static/codeindex.html HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/code/ Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 277 bytes. |
HTTP/1.1 200 OK
Date: Tue, 06 Feb 2024 17:42:23 GMT Expires: Tue, 06 Feb 2024 17:52:23 GMT Cache-Control: public, max-age=600 ETag: "3m8CBg" X-Cloud-Trace-Context: fb63ced220be6e1e9458c0baf24f1622 Content-Type: text/html Server: Google Frontend content-length: 2188 |
| Response Body - size: 2,188 bytes. |
<HTML>
<HEAD> <TITLE>Gruyere Code</TITLE> <STYLE> a {color:black; text-decoration:none} a:visited{color:#6600cc;} a:focus, a:hover {color:#003399; text-decoration: underline;} a:active{color:#cc33cc;} big {color: #660066; } </STYLE> </HEAD> <BODY> <TT> <B><BIG><BIG>Gruyere<BR>Code</BIG></BIG></B><BR><BR> <A href="/code/gruyere.py" target="codepage">gruyere.py</A><BR> <A href="/code/data.py" target="codepage">data.py</A><BR> <A href="/code/sanitize.py" target="codepage">sanitize.py</A><BR> <A href="/code/gtl.py" target="codepage">gtl.py</A><BR> <A href="/code/secret.txt" target="codepage">secret.txt</A><BR> <BR> resources/<BR> <A href="/code/resources/base.css" target="codepage">base.css</A><BR> <A href="/code/resources/dump.gtl" target="codepage">dump.gtl</A><BR> <A href="/code/resources/editprofile.gtl" target="codepage">editprofile.gtl</A><BR> <A href="/code/resources/error.gtl" target="codepage">error.gtl</A><BR> <A href="/code/resources/feed.gtl" target="codepage">feed.gtl</A><BR> <A href="/code/resources/home.gtl" target="codepage">home.gtl</A><BR> <A href="/code/resources/lib.js" target="codepage">lib.js</A><BR> <A href="/code/resources/login.gtl" target="codepage">login.gtl</A><BR> <A href="/code/resources/manage.gtl" target="codepage">manage.gtl</A><BR> <A href="/code/resources/menubar.gtl" target="codepage">menubar.gtl</A><BR> <A href="/code/resources/newaccount.gtl" target="codepage">newaccount.gtl</A><BR> <A href="/code/resources/newsnippet.gtl" target="codepage">newsnippet.gtl</A><BR> <A href="/code/resources/showprofile.gtl" target="codepage">showprofile.gtl</A><BR> <A href="/code/resources/snippets.gtl" target="codepage">snippets.gtl</A><BR> <A href="/code/resources/upload.gtl" target="codepage">upload.gtl</A><BR> <A href="/code/resources/upload2.gtl" target="codepage">upload2.gtl</A><BR> <BR> <I><A href="/gruyere-code.zip" target="_top">Download zip file</A></I><BR> <BR> <A href="/" target="_top"><< back to codelab</A> </TT> </BODY> </HTML> |
| URL | http://google-gruyere.appspot.com/static/codeindex/html |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 384 bytes. |
GET http://google-gruyere.appspot.com/static/codeindex/html HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/code/ Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 206 bytes. |
HTTP/1.1 404 Not Found
X-Cloud-Trace-Context: a8a7625fff7d27f6c180b7973800fcbf Date: Tue, 06 Feb 2024 17:42:23 GMT Content-Type: text/html; charset=UTF-8 Server: Google Frontend Content-Length: 298 |
| Response Body - size: 298 bytes. |
<html><head>
<meta http-equiv="content-type" content="text/html;charset=utf-8"> <title>404 Not Found</title> </head> <body text=#000000 bgcolor=#ffffff> <h1>Error: Not Found</h1> <h2>The requested URL <code>/static/codeindex/html</code> was not found on this server.</h2> <h2></h2> </body></html> |
| URL | https://google-gruyere.appspot.com/ |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 364 bytes. |
GET https://google-gruyere.appspot.com/ HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/part5 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 301 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: 8fa64cb955098684db5c4eca2daf22f7 Date: Tue, 06 Feb 2024 17:42:23 GMT Server: Google Frontend Content-Length: 11506 Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 |
| Response Body - size: 11,506 bytes. |
<HTML><HEAD><META http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<TITLE>Web Application Exploits and Defenses</TITLE> <LINK type="text/css" rel="stylesheet" href="../static/codelab.css"> <!--MARK-Z--> <SCRIPT> function toggleBlock(heading, whichID) { var image = heading.childNodes[0]; var block = document.getElementById(whichID); if (block) { if (getDisplay(block) == 'block') { block.style.display = 'none'; image.src = 'static/closed.gif'; } else { // "none" or "" block.style.display = 'block'; image.src = 'static/open.gif'; } } } function getDisplay(block) { var value = block.style.display; if (!value) { if (document.defaultView) { var computedStyle = document.defaultView.getComputedStyle(block, ""); value = computedStyle.getPropertyValue('display'); } else if (block.currentStyle) { value = block.currentStyle.display; } } return value; } </SCRIPT> </HEAD> <BODY bgcolor="#ffffff"> <DIV class="banner"></DIV> <H1><FONT size="+2"><IMG src="/static/gruyere-78.png" style="vertical-align:middle"> Web Application Exploits and Defenses <!--PART#--></FONT> </H1> <P> A Codelab by Bruce Leban, Mugdha Bendre, and Parisa Tabriz </P> <DIV class="printable"> <DIV class="column1"> Table of Contents <UL> <LI class="L1" style="padding-top:0pt"> <A href="/#0__hackers">Beat the hackers</A></LI> <LI class="L1" style="padding-top:0pt"> <A href="/#0__gruyere">Gruyere</A></LI> <LI class="L1"> <A href="/part1#1__setup">Set-up</A> <UL> <LI class="L2"> <A href="/part1#1__reset_button">Reset Button</A></LI> <LI class="L2"> <A href="/part1#1__about_the_code">About the Code</A></LI> <LI class="L2"> <A href="/part1#1__features_and_technologies">Features and Technologies</A></LI> </UL></LI> <LI class="L1"> <A href="/part1#1__using_gruyere">Using Gruyere</A></LI> <LI class="L1"> <A href="/part2#2__cross_site_scripting">Cross-Site Scripting (XSS)</A> <UL> <LI class="L2"> <A href="/part2#2__xss_challenge">XSS Challenges</A></LI> <LI class="L2"> <A href="/part2#2__file_upload_xss">File Upload XSS</A></LI> <LI class="L2"> <A href="/part2#2__reflected_xss">Reflected XSS</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss">Stored XSS</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss_via_html_attribute">Stored XSS via HTML Attribute</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss_via_ajax">Stored XSS via AJAX</A></LI> <LI class="L2"> <A href="/part2#2__reflected_xss_via_ajax">Reflected XSS via AJAX</A></LI> <LI class="L2"> <A href="/part2#2__more_about_xss">More about XSS</A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__client_state_manipulation">Client-State Manipulation</A> <UL> <LI class="L2"> <A href="/part3#3__elevation_of_privilege"> Elevation of Privilege </A></LI> <LI class="L2"> <A href="/part3#3__cookie_manipulation"> Cookie Manipulation </A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__cross_site_request_forgery">Cross-Site Request Forgery (XSRF)</A> <UL> <LI class="L2"> <A href="/part3#3__xsrf_challenge">XSRF Challenge</A></LI> <LI class="L2"> <A href="/part3#3__more_about_preventing_xsrf">More about preventing XSRF</A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__cross_site_script_inclusion">Cross Site Script Inclusion (XSSI)</A> <UL> <LI class="L2"> <A href="/part3#3__xssi_challenge">XSSI Challenge</A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__path_traversal">Path Traversal</A> <UL> <LI class="L2"> <A href="/part4#4__information_disclosure_path_traversal">Information disclosure via path traversal </A></LI> <LI class="L2"> <A href="/part4#4__data_tampering_path_traversal">Data tampering via path traversal </A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__denial_of_service">Denial of Service</A> <UL> <LI class="L2"> <A href="/part4#4__dos_quit_server">DoS - Quit the Server</A></LI> <LI class="L2"> <A href="/part4#4__dos_overload_server">DoS - Overloading the Server</A></LI> <LI class="L2"> <A href="/part4#4__more_dos">More on Denial of Service</A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__code_execution">Code Execution</A> <UL> <LI class="L2"> <A href="/part4#4__code_execution_challenge">Code Execution Challenge</A></LI> <LI class="L2"> <A href="/part4#4__more_code_execution">More on Remote Code Execution</A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__configuration_vulnerabilities">Configuration Vulnerabilities</A> <UL> <LI class="L2"> <A href="/part5#5__information_disclosure_config_1">Information disclosure #1</A></LI> <LI class="L2"> <A href="/part5#5__information_disclosure_config_2">Information disclosure #2</A></LI> <LI class="L2"> <A href="/part5#5__information_disclosure_bug_3">Information disclosure #3 </A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__ajax_vulnerabilities">AJAX vulnerabilities</A> <UL> <LI class="L2"> <A href="/part5#5__dos_via_ajax">DoS via AJAX</A></LI> <LI class="L2"> <A href="/part5#5__phishing_via_ajax">Phishing via AJAX</A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__other_vulnerabilities"> Other Vulnerabilities </A> </H2> <UL> <LI class="L2"> <A href="/part5#5__buffer_and_integer_overflow">Buffer Overflow and Integer Overflow</A></LI> <LI class="L2"> <A href="/part5#5__sql_injection"> SQL Injection </A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__after_the_codelab">After the Codelab</A></LI> </UL> </DIV> <DIV class="column2"> <P></P> <P></P> <P> <NOAUTOLINK> </NOAUTOLINK></P> <STYLE>.column1 {display:none}</STYLE> <BR><P> <H2><A name="0__hackers"></A>Want to beat the hackers at their own game?</H2> <UL> <LI>Learn how hackers find security vulnerabilities! <LI>Learn how hackers exploit web applications! <LI>Learn how to stop them! </UL> </P> <!--MARK-0--> <P> This codelab shows how web application vulnerabilities can be exploited and how to defend against these attacks. The best way to learn things is by doing, so you'll get a chance to do some real penetration testing, actually exploiting a real application. Specifically, you'll learn the following: </P> <P></P> <UL> <LI> How an application can be attacked using common web security vulnerabilities, like cross-site scripting vulnerabilities (XSS) and cross-site request forgery (XSRF). </LI> <LI> How to find, fix, and avoid these common vulnerabilities and other bugs that have a security impact, such as denial-of-service, information disclosure, or remote code execution. </LI> </UL> <P> To get the most out of this lab, you should have some familiarity with how a web application works (e.g., general knowledge of HTML, templates, cookies, AJAX, etc.). </P> <!--MARK-1--> <BR> <BR> <H2><A name="1__gruyere"> </A> Gruyere </H2> <P> <A href="/static/gruyere.png"> <IMG src="/static/gruyere.png" height="285" border="0" style="float:left; vertical-align:middle; margin-right: 10; margin-bottom: 10"> </A> This codelab is built around <B>Gruyere</B> /ɡruːˈjɛər/ <!--groo-yair--> - a small, cheesy web application that allows its users to publish snippets of text and store assorted files. "Unfortunately," Gruyere has multiple security bugs ranging from cross-site scripting and cross-site request forgery, to information disclosure, denial of service, and remote code execution. The goal of this codelab is to guide you through discovering some of these bugs and learning ways to fix them both in Gruyere and in general. </P> <P> The codelab is organized by types of vulnerabilities. In each section, you'll find a brief description of a vulnerability and a task to find an instance of that vulnerability in Gruyere. Your job is to play the role of a malicious hacker and find and exploit the security bugs. In this codelab, you'll use both black-box hacking and white-box hacking. In <B>black box hacking,</B> you try to find security bugs by experimenting with the application and manipulating input fields and URL parameters, trying to cause application errors, and looking at the HTTP requests and responses to guess server behavior. You do not have access to the source code, although understanding how to view source and being able to view http headers (as you can in Chrome or LiveHTTPHeaders for Firefox) is valuable. Using a web proxy like <A href="https://portswigger.net/burp/" target="_top">Burp</A> or <A href="https://www.owasp.org/index.php/OWASP_Zed_Attack_Proxy_Project" target="_top">ZAP</A> may be helpful in creating or modifying requests. In <B>white-box hacking,</B> you have access to the source code and can use automated or manual analysis to identify bugs. You can treat Gruyere as if it's open source: you can read through the source code to try to find bugs. Gruyere is written in Python, so some familiarity with Python can be helpful. However, the security vulnerabilities covered are not Python-specific and you can do most of the lab without even looking at the code. You can run a local instance of Gruyere to assist in your hacking: for example, you can create an administrator account on your local instance to learn how administrative features work and then apply that knowledge to the instance you want to hack. Security researchers use both hacking techniques, often in combination, in real life. </P> <BR clear="left"> We'll tag each challenge to indicate which techniques are required to solve them: <BR><BR> <IMG src="/static/cheese_b.png" style="vertical-align:middle; padding-right: 5px;"> Challenges that can be solved just by using black box techniques.<BR><BR> <IMG src="/static/cheese_w.png" style="vertical-align:middle; padding-right: 5px;"> Challenges that require that you look at the Gruyere source code.<BR><BR> <IMG src="/static/cheese_bw.png" style="vertical-align:middle; padding-right: 5px;"> Challenges that require some specific knowledge of Gruyere that will be given in the first hint. <BR> <P style="color:red"> <B>WARNING:</B> Accessing or attacking a computer system without authorization is illegal in many jurisdictions. While doing this codelab, you are specifically granted authorization to attack the Gruyere application as directed. You may not attack Gruyere in ways other than described in this codelab, nor may you attack App Engine directly or any other Google service. You should use what you learn from the codelab to make your own applications more secure. You should not use it to attack any applications other than your own, and only do that with permission from the appropriate authorities (e.g., your company's security team). </P> <P></P> <FONT SIZE="+2"> <A href="/part1">Continue >></A> </FONT><BR> <BR> <P style="font-size:x-small"> © Google 2017 <A href="https://www.google.com/intl/en/policies/terms/">Terms of Service</A> <BR>The code portions of this codelab are licensed under the Creative Commons Attribution-No Derivative Works 3.0 United States license <<A href="https://creativecommons.org/licenses/by-nd/3.0/us/">https://creativecommons.org/licenses/by-nd/3.0/us</A>>. Brief excerpts of the code may be used for educational or instructional purposes provided this notice is kept intact. Except as otherwise noted the remainder of this codelab is licensed under the Creative Commons Attribution 3.0 United States license <<A href="https://creativecommons.org/licenses/by/3.0/us/">https://creativecommons.org/licenses/by/3.0/us</A>>. </P> </BODY></HTML> |
| URL | https://google-gruyere.appspot.com/382665580745386307547168512335551204731/ |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 451 bytes. |
GET https://google-gruyere.appspot.com/382665580745386307547168512335551204731/ HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: https://google-gruyere.appspot.com/382665580745386307547168512335551204731/quitserver. Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 306 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: 1b18c15e61fbec8b3e4949b4ddc0dc67 Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 3480 Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 |
| Response Body - size: 3,480 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Home</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> <script src="/382665580745386307547168512335551204731/lib.js" text="text/javascript"> </script> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/382665580745386307547168512335551204731/'>Home</a> </span> <span id='menu-right'> <a href='/382665580745386307547168512335551204731/login'>Sign in</a> | <a href='/382665580745386307547168512335551204731/newaccount.gtl'>Sign up</a> </span> </div> <div> <h2 class='has-refresh'>Gruyere: Home</h2> <div class='refresh'><a class='button' onclick='_refreshHome("382665580745386307547168512335551204731")' href='#'>Refresh</a></div> </div> <div class='content'> <table width='100%'> <tr><td colspan='3'><b>Most recent snippets:</b></td></tr> <tr> <td> </td> <td> <b><span style='color:blue'>Cheddar Mac</span></b> </td> <td width='100%'><span id='cheddar'>Gruyere is the cheesiest application on the web.</span> <br> <a href='/382665580745386307547168512335551204731/snippets.gtl?uid=cheddar'>All snippets</a> <a href='https://images.google.com/?q=cheddar+cheese'>Homepage</a> <br> <br> </td> </tr> <tr> <td> </td> <td> <b><span style='color:red; text-decoration:underline'>Brie</span></b> </td> <td width='100%'><span id='brie'>Brie is the queen of the cheeses<span style=color:red>!!!</span></span> <br> <a href='/382665580745386307547168512335551204731/snippets.gtl?uid=brie'>All snippets</a> <a href='https://news.google.com/news/search?q=brie'>Homepage</a> <br> <br> </td> </tr> </table> </div> </body> </html> |
| URL | https://google-gruyere.appspot.com/382665580745386307547168512335551204731/feed.gtl |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 412 bytes. |
GET https://google-gruyere.appspot.com/382665580745386307547168512335551204731/feed.gtl HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/part2 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 305 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: 0d6b4d9f18f7cecd56fd804c68263bbf Date: Tue, 06 Feb 2024 17:42:23 GMT Server: Google Frontend Content-Length: 191 Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 |
| Response Body - size: 191 bytes. |
_feed((
{ "private_snippet": "" ,"cheddar": "Gruyere is the cheesiest application on the web." ,"brie": "Brie is the queen of the cheeses<span style=color:red>!!!</span>" } )) |
| URL | https://google-gruyere.appspot.com/382665580745386307547168512335551204731/login |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 456 bytes. |
GET https://google-gruyere.appspot.com/382665580745386307547168512335551204731/login HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: https://google-gruyere.appspot.com/382665580745386307547168512335551204731/quitserver. Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 306 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: 8b42cbce881c35ee8d87883b85cd8f7f Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 2591 Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 |
| Response Body - size: 2,591 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Login</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/382665580745386307547168512335551204731/'>Home</a> </span> <span id='menu-right'> <a href='/382665580745386307547168512335551204731/login'>Sign in</a> | <a href='/382665580745386307547168512335551204731/newaccount.gtl'>Sign up</a> </span> </div> <div> <h2>Gruyere: Login</h2> </div> <div class='content'> <form method='get' action='/382665580745386307547168512335551204731/login'> <table><tr><td> User name: </td><td> <input type='text' name='uid'> </td></tr><tr><td> Password: </td><td> <input type='password' name='pw'> </td></tr><tr><td></td><td align='right'> <input type='submit' value='Login'> </td></tr></table> </form> </div> </body> </html> |
| URL | https://google-gruyere.appspot.com/382665580745386307547168512335551204731/newaccount.gtl |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 465 bytes. |
GET https://google-gruyere.appspot.com/382665580745386307547168512335551204731/newaccount.gtl HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: https://google-gruyere.appspot.com/382665580745386307547168512335551204731/quitserver. Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 306 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: e1f0d836f241aeea4e12446db611b471 Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 2961 Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 |
| Response Body - size: 2,961 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Profile</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/382665580745386307547168512335551204731/'>Home</a> </span> <span id='menu-right'> <a href='/382665580745386307547168512335551204731/login'>Sign in</a> | <a href='/382665580745386307547168512335551204731/newaccount.gtl'>Sign up</a> </span> </div> <h2>Gruyere: Sign up</h2> <div class='content'> <h3>Sign up for a new account.</h3> <form method='get' action='/382665580745386307547168512335551204731/saveprofile'> <input type='hidden' name='action' value='new'> <table><tr><td> User name: </td><td> <input type='text' name='uid' value='' maxlength='16'> </td></tr> <tr><td> Password: </td><td> <input type='password' name='pw'> <br><span style="color:red"><b>WARNING: Gruyere is not secure.<br> Do not use a password that you use for any real service.<br> Do not upload any personal or private data.</b></span> </td></tr> <input type='hidden' name='is_author' value='True'> <tr><td></td><td align='right'> <input type='submit' value='Create account'> </td></tr> </table> </form> </div> </body> </html> |
| URL | https://google-gruyere.appspot.com/382665580745386307547168512335551204731/quitserver. |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 415 bytes. |
GET https://google-gruyere.appspot.com/382665580745386307547168512335551204731/quitserver. HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/part4 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 306 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: ba514b4b962961f93b1c4fbc21fab959 Date: Tue, 06 Feb 2024 17:42:23 GMT Server: Google Frontend Content-Length: 2246 Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 |
| Response Body - size: 2,246 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Error</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/382665580745386307547168512335551204731/'>Home</a> </span> <span id='menu-right'> <a href='/382665580745386307547168512335551204731/login'>Sign in</a> | <a href='/382665580745386307547168512335551204731/newaccount.gtl'>Sign up</a> </span> </div> <div class='message'>Unrecognized file type (/quitserver.).</div> </body> </html> |
| URL | https://google-gruyere.appspot.com/382665580745386307547168512335551204731/RESET. |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 410 bytes. |
GET https://google-gruyere.appspot.com/382665580745386307547168512335551204731/RESET. HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/part4 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 306 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: 07f510b6cdd84a858cf4d3c73829ba7a Date: Tue, 06 Feb 2024 17:42:23 GMT Server: Google Frontend Content-Length: 2241 Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 |
| Response Body - size: 2,241 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Error</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/382665580745386307547168512335551204731/'>Home</a> </span> <span id='menu-right'> <a href='/382665580745386307547168512335551204731/login'>Sign in</a> | <a href='/382665580745386307547168512335551204731/newaccount.gtl'>Sign up</a> </span> </div> <div class='message'>Unrecognized file type (/RESET.).</div> </body> </html> |
| URL | https://google-gruyere.appspot.com/382665580745386307547168512335551204731/saveprofile?action=update&is_admin=True |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 443 bytes. |
GET https://google-gruyere.appspot.com/382665580745386307547168512335551204731/saveprofile?action=update&is_admin=True HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/part3 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 306 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: 49fab23ad5f50929949b343099495117 Date: Tue, 06 Feb 2024 17:42:23 GMT Server: Google Frontend Content-Length: 2228 Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 |
| Response Body - size: 2,228 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Error</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/382665580745386307547168512335551204731/'>Home</a> </span> <span id='menu-right'> <a href='/382665580745386307547168512335551204731/login'>Sign in</a> | <a href='/382665580745386307547168512335551204731/newaccount.gtl'>Sign up</a> </span> </div> <div class='message'>User does not exist.</div> </body> </html> |
| URL | https://google-gruyere.appspot.com/382665580745386307547168512335551204731/saveprofile?action=update&is_admin=True&uid=username |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 456 bytes. |
GET https://google-gruyere.appspot.com/382665580745386307547168512335551204731/saveprofile?action=update&is_admin=True&uid=username HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/part3 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 306 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: 1c2eebd839ba8c29a02e769d0f003a2e Date: Tue, 06 Feb 2024 17:42:23 GMT Server: Google Frontend Content-Length: 2228 Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 |
| Response Body - size: 2,228 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Error</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/382665580745386307547168512335551204731/'>Home</a> </span> <span id='menu-right'> <a href='/382665580745386307547168512335551204731/login'>Sign in</a> | <a href='/382665580745386307547168512335551204731/newaccount.gtl'>Sign up</a> </span> </div> <div class='message'>User does not exist.</div> </body> </html> |
| URL | https://google-gruyere.appspot.com/code/ |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 369 bytes. |
GET https://google-gruyere.appspot.com/code/ HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/part1 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 303 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: e9bcd98bd046d94c037b912b5eabbbf3;o=1 Date: Tue, 06 Feb 2024 17:42:22 GMT Server: Google Frontend Content-Length: 354 Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 |
| Response Body - size: 354 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN"
"https://www.w3.org/TR/html4/frameset.dtd"> <HTML> <HEAD> <TITLE>Gruyere Code</TITLE> </HEAD> <FRAMESET cols="165,100%"> <FRAME src="/static/codeindex.html"> <FRAME name="codepage" src=""> <NOFRAMES> <A href="/static/codeindex/html">Code Index</A> </NOFRAMES> </FRAMESET> </HTML> |
| URL | https://google-gruyere.appspot.com/part1 |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 365 bytes. |
GET https://google-gruyere.appspot.com/part1 HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: https://google-gruyere.appspot.com/ Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 301 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: ec0d0e195a8e5f5598cf104482954f77 Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 13686 Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 |
| Response Body - size: 13,686 bytes. |
<HTML><HEAD><META http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<TITLE>Web Application Exploits and Defenses</TITLE> <LINK type="text/css" rel="stylesheet" href="../static/codelab.css"> <!--MARK-Z--> <SCRIPT> function toggleBlock(heading, whichID) { var image = heading.childNodes[0]; var block = document.getElementById(whichID); if (block) { if (getDisplay(block) == 'block') { block.style.display = 'none'; image.src = 'static/closed.gif'; } else { // "none" or "" block.style.display = 'block'; image.src = 'static/open.gif'; } } } function getDisplay(block) { var value = block.style.display; if (!value) { if (document.defaultView) { var computedStyle = document.defaultView.getComputedStyle(block, ""); value = computedStyle.getPropertyValue('display'); } else if (block.currentStyle) { value = block.currentStyle.display; } } return value; } </SCRIPT> </HEAD> <BODY bgcolor="#ffffff"> <DIV class="banner"></DIV> <H1><FONT size="+2"><IMG src="/static/gruyere-78.png" style="vertical-align:middle"> Web Application Exploits and Defenses (Part 1)</FONT> </H1> <P> A Codelab by Bruce Leban, Mugdha Bendre, and Parisa Tabriz </P> <DIV class="printable"> <DIV class="column1"> Table of Contents <UL> <LI class="L1" style="padding-top:0pt"> <A href="/#0__hackers">Beat the hackers</A></LI> <LI class="L1" style="padding-top:0pt"> <A href="/#0__gruyere">Gruyere</A></LI> <LI class="L1"> <A href="/part1#1__setup">Set-up</A> <UL> <LI class="L2"> <A href="/part1#1__reset_button">Reset Button</A></LI> <LI class="L2"> <A href="/part1#1__about_the_code">About the Code</A></LI> <LI class="L2"> <A href="/part1#1__features_and_technologies">Features and Technologies</A></LI> </UL></LI> <LI class="L1"> <A href="/part1#1__using_gruyere">Using Gruyere</A></LI> <LI class="L1"> <A href="/part2#2__cross_site_scripting">Cross-Site Scripting (XSS)</A> <UL> <LI class="L2"> <A href="/part2#2__xss_challenge">XSS Challenges</A></LI> <LI class="L2"> <A href="/part2#2__file_upload_xss">File Upload XSS</A></LI> <LI class="L2"> <A href="/part2#2__reflected_xss">Reflected XSS</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss">Stored XSS</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss_via_html_attribute">Stored XSS via HTML Attribute</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss_via_ajax">Stored XSS via AJAX</A></LI> <LI class="L2"> <A href="/part2#2__reflected_xss_via_ajax">Reflected XSS via AJAX</A></LI> <LI class="L2"> <A href="/part2#2__more_about_xss">More about XSS</A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__client_state_manipulation">Client-State Manipulation</A> <UL> <LI class="L2"> <A href="/part3#3__elevation_of_privilege"> Elevation of Privilege </A></LI> <LI class="L2"> <A href="/part3#3__cookie_manipulation"> Cookie Manipulation </A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__cross_site_request_forgery">Cross-Site Request Forgery (XSRF)</A> <UL> <LI class="L2"> <A href="/part3#3__xsrf_challenge">XSRF Challenge</A></LI> <LI class="L2"> <A href="/part3#3__more_about_preventing_xsrf">More about preventing XSRF</A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__cross_site_script_inclusion">Cross Site Script Inclusion (XSSI)</A> <UL> <LI class="L2"> <A href="/part3#3__xssi_challenge">XSSI Challenge</A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__path_traversal">Path Traversal</A> <UL> <LI class="L2"> <A href="/part4#4__information_disclosure_path_traversal">Information disclosure via path traversal </A></LI> <LI class="L2"> <A href="/part4#4__data_tampering_path_traversal">Data tampering via path traversal </A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__denial_of_service">Denial of Service</A> <UL> <LI class="L2"> <A href="/part4#4__dos_quit_server">DoS - Quit the Server</A></LI> <LI class="L2"> <A href="/part4#4__dos_overload_server">DoS - Overloading the Server</A></LI> <LI class="L2"> <A href="/part4#4__more_dos">More on Denial of Service</A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__code_execution">Code Execution</A> <UL> <LI class="L2"> <A href="/part4#4__code_execution_challenge">Code Execution Challenge</A></LI> <LI class="L2"> <A href="/part4#4__more_code_execution">More on Remote Code Execution</A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__configuration_vulnerabilities">Configuration Vulnerabilities</A> <UL> <LI class="L2"> <A href="/part5#5__information_disclosure_config_1">Information disclosure #1</A></LI> <LI class="L2"> <A href="/part5#5__information_disclosure_config_2">Information disclosure #2</A></LI> <LI class="L2"> <A href="/part5#5__information_disclosure_bug_3">Information disclosure #3 </A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__ajax_vulnerabilities">AJAX vulnerabilities</A> <UL> <LI class="L2"> <A href="/part5#5__dos_via_ajax">DoS via AJAX</A></LI> <LI class="L2"> <A href="/part5#5__phishing_via_ajax">Phishing via AJAX</A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__other_vulnerabilities"> Other Vulnerabilities </A> </H2> <UL> <LI class="L2"> <A href="/part5#5__buffer_and_integer_overflow">Buffer Overflow and Integer Overflow</A></LI> <LI class="L2"> <A href="/part5#5__sql_injection"> SQL Injection </A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__after_the_codelab">After the Codelab</A></LI> </UL> </DIV> <DIV class="column2"> <P></P> <P></P> <P> <NOAUTOLINK> </NOAUTOLINK></P> <H2><A name="1__setup"> </A> Setup </H2> <P> To access Gruyere, go to <CODE><A href="/start">https://google-gruyere.appspot.com/start</A></CODE>. AppEngine will start a new instance of Gruyere for you, assign it a unique id and redirect you to <CODE>https://google-gruyere.appspot.com/<!--do not replace-->123/</CODE> (where <CODE>123</CODE> is your unique id). Each instance of Gruyere is "sandboxed" from the other instances so your instance won't be affected by anyone else using Gruyere. You'll need to use your unique id instead of <CODE>123</CODE> in all the examples. If you want to share your instance of Gruyere with someone else (e.g., to show them a successful attack), just share the full URL with them including your unique id. </P> <P>The Gruyere source code is available online so that you can use it for white-box hacking. You can browse the source code at <A href="/code/">https://google-gruyere.appspot.com/code/</A> or download all the files from <A href="/gruyere-code.zip">https://google-gruyere.appspot.com/gruyere-code.zip</A>. If want to debug it or actually try fixing the bugs, you can download it and run it locally. You do not need to run Gruyere locally in order to do the lab. </P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'running_locally');"><IMG src="/static/closed.gif"> Running locally </H4> <DIV id="running_locally" style="display:none"> <P> <FONT color="#ff0000"> <B>WARNING:</B> Because Gruyere is very vulnerable, it includes some protection against being exploited by an external attacker when run locally. You'll see these parts of the code marked DO NOT CHANGE. Gruyere only accepts requests from localhost and uses a random unique id in the URL. However, it's difficult to fully protect against an external attack. And if you make changes to Gruyere you could make it more vulnerable to a real attack. Therefore, you should close other web pages while running Gruyere locally and you should make sure that no other user is logged in to the machine you are using. </FONT> </P> <P> To run Gruyere locally, you'll first need to install Python 2.7, if you don't already have it. Gruyere was developed and tested with version 2.7 and may not work with other versions of Python. You can download it from <A href="https://www.python.org/downloads/" target="_top">python.org</A>. Download Gruyere itself from <A href="/gruyere-code.zip">https://google-gruyere.appspot.com/gruyere-code.zip</A> and unpack it to your local disk. Then to run the application, simply type: </P> <P></P> <PRE> $ cd <gruyere-directory> $ ./gruyere.py</PRE> <P> You'll need to replace <CODE>google-gruyere.appspot.com</CODE> in all the examples with <CODE>localhost:8008</CODE> in addition to replacing <CODE>123</CODE> with your unique id. Note that the unique id appears in a different location. There are a few other small differences between running Gruyere locally vs. accessing the instance on App Engine. The most obvious is that the App Engine version runs in a limited sandbox. So if you do something that puts Gruyere into an infinite loop, the monitor will detect it and kill it. That might not happen when you run it locally, depending on what the loop is doing. </P> </DIV> </DIV> <BR> <H3><A name="1__reset_button"> </A> Reset Button </H3> As noted above, each instance is sandboxed so it can't consume infinite resources and it can't interfere with anyone else's instance. Notwithstanding that, it is possible to put your Gruyere instance into a state where it is completely unusable. If that happens, you can push a magic "reset button" to wipe out all the data in your instance and start from scratch. To do this, visit this URL with your instance id: <PRE> https://google-gruyere.appspot.com/resetbutton/382665580745386307547168512335551204731 </PRE> <BR> <H3><A name="1__about_the_code"> </A> About the Code </H3> <P> Gruyere is small and compact. Here is a quick rundown of the application code: </P><UL> <LI> <CODE><A href="/code/?gruyere.py">gruyere.py</A></CODE> is the main Gruyere web server </LI> <LI> <CODE><A href="/code/?data.py">data.py</A></CODE> stores the default data in the database. There is an administrator account and two default users. </LI> <LI> <CODE><A href="/code/?gtl.py">gtl.py</A></CODE> is the Gruyere template language </LI> <LI> <CODE><A href="/code/?sanitize.py">sanitize.py</A></CODE> is the Gruyere module used for sanitizing HTML to protect the application from security holes. </LI> <LI> <CODE><A href="/code/">resources/...</A></CODE> holds all template files, images, CSS, etc. </LI> </UL> <P></P> <BR> <H3><A name="1__features_and_technologies"> </A> Features and Technologies </H3> <P> Gruyere includes a number of special features and technologies which add attack surface. We'll highlight them here so you'll be aware of them as you try to attack it. Each of these introduces new vulnerabilities. </P> <P></P> <UL> <LI> HTML in Snippets: Users can include a limited subset of HTML in their snippets. </LI> <LI> File upload: Users can upload files to the server, e.g., to include pictures in their snippets. </LI> <LI> Web administration: System administrators can manage the system using a web interface. </LI> <LI> New accounts: Users can create their own accounts. </LI> <LI> Template language: Gruyere Template Language(GTL) is a new language that makes writing web pages easy as the templates connect directly to the database. Documentation for GTL can be found in <CODE><A href="/code/?gtl.py">gruyere/gtl.py</A></CODE>. </LI> <LI> AJAX: Gruyere uses AJAX to implement refresh on the home and snippets page. You should ignore the AJAX parts of Gruyere except for the challenges that specifically tell you to focus on AJAX. <UL> <LI> In a real application, refresh would probably happen automatically, but in Gruyere we've made it manual so that you can be in complete control while you are working with it. When you click the refresh link, Gruyere fetches <CODE><A href="/code/?resources/feed.gtl">feed.gtl</A></CODE> which contains refresh data for the current page and then client-side script uses the browser DOM API (Document Object Model) to insert the new snippets into the page. Since AJAX runs code on the client side, this script is visible to attackers who do not have access to your source code. </LI> </UL> </LI> </UL> <P></P> <BR> <H2><A name="1__using_gruyere"> </A> Using Gruyere </H2> <P> To familiarize yourself with the features of Gruyere, complete the following tasks: </P> <P></P> <UL> <LI> View another user's snippets by following the "All snippets" link on the main page. Also check out what they have their Homepage set to. </LI> <LI> Sign up for an account for yourself to use when hacking. <B>Do not use the same password for your Gruyere account as you use for any real service.</B> </LI> <LI> Fill in your account's profile, including a private snippet and an icon that will be displayed by your name. </LI> <LI> Create a snippet (via "New Snippet") containing your favorite joke. </LI> <LI> Upload a file (via "Upload") to your account. </LI> </UL> <P></P> <P> This covers the basic features provided by Gruyere. Now let's break them! </P> <BR><P></P> <FONT SIZE="+2"> <A href="/part2">Continue >></A> </FONT><BR> <BR> <P style="font-size:x-small"> © Google 2017 <A href="https://www.google.com/intl/en/policies/terms/">Terms of Service</A> <BR>The code portions of this codelab are licensed under the Creative Commons Attribution-No Derivative Works 3.0 United States license <<A href="https://creativecommons.org/licenses/by-nd/3.0/us/">https://creativecommons.org/licenses/by-nd/3.0/us</A>>. Brief excerpts of the code may be used for educational or instructional purposes provided this notice is kept intact. Except as otherwise noted the remainder of this codelab is licensed under the Creative Commons Attribution 3.0 United States license <<A href="https://creativecommons.org/licenses/by/3.0/us/">https://creativecommons.org/licenses/by/3.0/us</A>>. </P> </BODY></HTML> |
| URL | https://google-gruyere.appspot.com/part2 |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 365 bytes. |
GET https://google-gruyere.appspot.com/part2 HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: https://google-gruyere.appspot.com/ Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 301 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: db540cb7d57c537e646ca96132050405 Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 37564 Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 |
| Response Body - size: 37,564 bytes. |
<HTML><HEAD><META http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<TITLE>Web Application Exploits and Defenses</TITLE> <LINK type="text/css" rel="stylesheet" href="../static/codelab.css"> <!--MARK-Z--> <SCRIPT> function toggleBlock(heading, whichID) { var image = heading.childNodes[0]; var block = document.getElementById(whichID); if (block) { if (getDisplay(block) == 'block') { block.style.display = 'none'; image.src = 'static/closed.gif'; } else { // "none" or "" block.style.display = 'block'; image.src = 'static/open.gif'; } } } function getDisplay(block) { var value = block.style.display; if (!value) { if (document.defaultView) { var computedStyle = document.defaultView.getComputedStyle(block, ""); value = computedStyle.getPropertyValue('display'); } else if (block.currentStyle) { value = block.currentStyle.display; } } return value; } </SCRIPT> </HEAD> <BODY bgcolor="#ffffff"> <DIV class="banner"></DIV> <H1><FONT size="+2"><IMG src="/static/gruyere-78.png" style="vertical-align:middle"> Web Application Exploits and Defenses (Part 2)</FONT> </H1> <P> A Codelab by Bruce Leban, Mugdha Bendre, and Parisa Tabriz </P> <DIV class="printable"> <DIV class="column1"> Table of Contents <UL> <LI class="L1" style="padding-top:0pt"> <A href="/#0__hackers">Beat the hackers</A></LI> <LI class="L1" style="padding-top:0pt"> <A href="/#0__gruyere">Gruyere</A></LI> <LI class="L1"> <A href="/part1#1__setup">Set-up</A> <UL> <LI class="L2"> <A href="/part1#1__reset_button">Reset Button</A></LI> <LI class="L2"> <A href="/part1#1__about_the_code">About the Code</A></LI> <LI class="L2"> <A href="/part1#1__features_and_technologies">Features and Technologies</A></LI> </UL></LI> <LI class="L1"> <A href="/part1#1__using_gruyere">Using Gruyere</A></LI> <LI class="L1"> <A href="/part2#2__cross_site_scripting">Cross-Site Scripting (XSS)</A> <UL> <LI class="L2"> <A href="/part2#2__xss_challenge">XSS Challenges</A></LI> <LI class="L2"> <A href="/part2#2__file_upload_xss">File Upload XSS</A></LI> <LI class="L2"> <A href="/part2#2__reflected_xss">Reflected XSS</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss">Stored XSS</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss_via_html_attribute">Stored XSS via HTML Attribute</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss_via_ajax">Stored XSS via AJAX</A></LI> <LI class="L2"> <A href="/part2#2__reflected_xss_via_ajax">Reflected XSS via AJAX</A></LI> <LI class="L2"> <A href="/part2#2__more_about_xss">More about XSS</A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__client_state_manipulation">Client-State Manipulation</A> <UL> <LI class="L2"> <A href="/part3#3__elevation_of_privilege"> Elevation of Privilege </A></LI> <LI class="L2"> <A href="/part3#3__cookie_manipulation"> Cookie Manipulation </A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__cross_site_request_forgery">Cross-Site Request Forgery (XSRF)</A> <UL> <LI class="L2"> <A href="/part3#3__xsrf_challenge">XSRF Challenge</A></LI> <LI class="L2"> <A href="/part3#3__more_about_preventing_xsrf">More about preventing XSRF</A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__cross_site_script_inclusion">Cross Site Script Inclusion (XSSI)</A> <UL> <LI class="L2"> <A href="/part3#3__xssi_challenge">XSSI Challenge</A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__path_traversal">Path Traversal</A> <UL> <LI class="L2"> <A href="/part4#4__information_disclosure_path_traversal">Information disclosure via path traversal </A></LI> <LI class="L2"> <A href="/part4#4__data_tampering_path_traversal">Data tampering via path traversal </A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__denial_of_service">Denial of Service</A> <UL> <LI class="L2"> <A href="/part4#4__dos_quit_server">DoS - Quit the Server</A></LI> <LI class="L2"> <A href="/part4#4__dos_overload_server">DoS - Overloading the Server</A></LI> <LI class="L2"> <A href="/part4#4__more_dos">More on Denial of Service</A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__code_execution">Code Execution</A> <UL> <LI class="L2"> <A href="/part4#4__code_execution_challenge">Code Execution Challenge</A></LI> <LI class="L2"> <A href="/part4#4__more_code_execution">More on Remote Code Execution</A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__configuration_vulnerabilities">Configuration Vulnerabilities</A> <UL> <LI class="L2"> <A href="/part5#5__information_disclosure_config_1">Information disclosure #1</A></LI> <LI class="L2"> <A href="/part5#5__information_disclosure_config_2">Information disclosure #2</A></LI> <LI class="L2"> <A href="/part5#5__information_disclosure_bug_3">Information disclosure #3 </A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__ajax_vulnerabilities">AJAX vulnerabilities</A> <UL> <LI class="L2"> <A href="/part5#5__dos_via_ajax">DoS via AJAX</A></LI> <LI class="L2"> <A href="/part5#5__phishing_via_ajax">Phishing via AJAX</A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__other_vulnerabilities"> Other Vulnerabilities </A> </H2> <UL> <LI class="L2"> <A href="/part5#5__buffer_and_integer_overflow">Buffer Overflow and Integer Overflow</A></LI> <LI class="L2"> <A href="/part5#5__sql_injection"> SQL Injection </A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__after_the_codelab">After the Codelab</A></LI> </UL> </DIV> <DIV class="column2"> <P></P> <P></P> <P> <NOAUTOLINK> </NOAUTOLINK></P> <H2><A name="2__cross_site_scripting"> </A> Cross-Site Scripting (XSS) </H2> <P> Cross-site scripting (XSS) is a vulnerability that permits an attacker to inject code (typically HTML or JavaScript) into contents of a website not under the attacker's control. When a victim views such a page, the injected code executes in the victim's browser. Thus, the attacker has bypassed the browser's <A href="https://www.google.com/search?q=same+origin+policy">same origin policy</A> and can steal victim's private information associated with the website in question. </P> <P> In a <b>reflected XSS</b> attack, the attack is in the request itself (frequently the URL) and the vulnerability occurs when the server inserts the attack in the response verbatim or incorrectly escaped or sanitized. The victim triggers the attack by browsing to a malicious URL created by the attacker. In a <b>stored XSS</b> attack, the attacker stores the attack in the application (e.g., in a snippet) and the victim triggers the attack by browsing to a page on the server that renders the attack, by not properly escaping or sanitizing the stored data. </P> <P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_desc');"><IMG src="/static/closed.gif"> More details </H4> <DIV id="xss_desc" style="display:none"> <P></P> <P> To understand how this could happen: suppose the URL <CODE>https://www.google.com/search?q=flowers</CODE> returns a page containing the HTML fragment </P> <P></P> <PRE> <p>Your search for 'flowers' returned the following results:</p> </PRE> <P> that is, the value of the query parameter <CODE>q</CODE> is inserted verbatim into the page returned by Google. If <CODE>www.google.com</CODE> did not do any validation or escaping of <CODE>q</CODE> (it does), an attacker could craft a link that looks like this:<BR> <PRE> https://www.google.com/search?q=flowers+%3Cscript%3Eevil_script()%3C/script%3E </PRE> and trick a victim into clicking on this link. When a victim loads this link, the following page gets rendered in the victim's browser: </P> <P></P> <PRE> <p>Your search for 'flowers<script>evil_script()</script>' returned the following results:</p> </PRE> <P> And the browser executes <CODE>evil_script()</CODE>. And since the page comes from <CODE>www.google.com</CODE>, <CODE>evil_script()</CODE> is executed in the context of <CODE>www.google.com</CODE> and has access to all the victim's browser state and cookies for that domain. </P> <P> Note that the victim does not even need to explicitly click on the malicious link. Suppose the attacker owns <CODE>www.evil.example.com</CODE>, and creates a page with an <CODE><iframe></CODE> pointing to the malicious link; if the victim visits <CODE>www.evil.example.com</CODE>, the attack will silently be activated. </P> </DIV> </DIV> <P></P> <H3><A name="2__xss_challenge"> </A> XSS Challenges </H3> <P> Typically, if you can get JavaScript to execute on a page when it's viewed by another user, you have an XSS vulnerability. A simple JavaScript function to use when hacking is the <CODE>alert()</CODE> function, which creates a pop-up box with whatever string you pass as an argument. </P> <P>You might think that inserting an alert message isn't terribly dangerous, but if you can inject that, you can inject other scripts that are more malicious. It is not necessary to be able to inject any particular special character in order to attack. If you can inject <CODE>alert(1)</CODE> then you can inject arbitrary script using <CODE>eval(String.fromCharCode(...))</CODE>. </P> <P> Your challenge is to find XSS vulnerabilities in Gruyere. You should look for vulnerabilities both in URLs and in stored data. Since XSS vulnerabilities usually involve applications not properly handling untrusted user data, a common method of attack is to enter random text in input fields and look at how it gets rendered in the response page's HTML source. But before we do that, let's try something simpler. </P> <P></P> <H3><A name="2__file_upload_xss"> </A> File Upload XSS </H3> <P> <IMG src="/static/cheese_b.png" style="vertical-align:middle; padding-right: 5px;"> <B>Can you upload a file that allows you to execute arbitrary script on the <CODE>google-gruyere.appspot.com</CODE> domain?</B> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'upload_xss_hint1');"><IMG src="/static/closed.gif"> Hint </H4> <DIV id="upload_xss_hint1" style="display:none"> <P> You can upload HTML files and HTML files can contain script. </P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'upload_xss_sol');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="upload_xss_sol" style="display:none"> <P><B>To exploit,</B> upload a <CODE>.html</CODE> file containing a script like this: <PRE> <script> alert(document.cookie); </script> </PRE> </P><P> <B>To fix,</B> host the content on a separate domain so the script won't have access to any content from your domain. That is, instead of hosting user content on <CODE>example.com/<I>username</I></CODE> we would host it at <CODE><I>username</I>.usercontent.example.com</CODE> or <CODE><I>username</I>.example-usercontent.com</CODE>. (Including something like "<CODE>usercontent</CODE>" in the domain name avoids attackers registering usernames that look innocent like <CODE>wwww</CODE> and using them for phishing attacks.) <P> </DIV> </DIV> <H3><A name="2__reflected_xss"> </A> Reflected XSS </H3> <P> There's an interesting problem here. Some browsers have built-in protection against reflected XSS attacks. There are also browser extensions like NoScript that provide some protection. If you're using one of those browsers or extensions, you may need to use a different browser or temporarily disable the extension to execute these attacks. </P> <P>At the time this codelab was written, the two browsers which had this protection were IE and Chrome. To work around this, Gruyere automatically includes a <TT>X-XSS-Protection: 0</TT> HTTP header in every response which is recognized by IE and will be recognized by future versions of Chrome. (It's available in the developer channel now.) If you're using Chrome, you can try starting it with the <TT>--disable-xss-auditor</TT> flag by entering one of these commands: <UL><LI>Windows: <TT>"C:\Documents and Settings\USERNAME\Local Settings\Application Data\Google\Chrome\Application\chrome.exe" --disable-xss-auditor</TT> <LI>Mac: <TT>/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --disable-xss-auditor</TT> <LI>GNU/Linux: <TT>/opt/google/chrome/google-chrome --disable-xss-auditor</TT> </UL> If you're using Firefox with the NoScript extension, add <TT>google-gruyere.appspot.com</TT> to the allow list. If you still can't get the XSS attacks to work, try a different browser. </P> <P>You may think that you don't need to worry about XSS if the browser protects against it. The truth is that the browser protection can't be perfect because it doesn't really know your application and therefore there may be ways for a clever hacker to circumvent that protection. The real protection is to not have an XSS vulnerability in your application in the first place. </P> <IMG src="/static/cheese_b.png" style="vertical-align:middle; padding-right: 5px;"> <B>Find a reflected XSS attack. What we want is a URL that when clicked on will execute a script.</B> <P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_url_hint1');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="xss_url_hint1" style="display:none"> <P> What does this URL do? </P> <P></P> <PRE> https://google-gruyere.appspot.com/382665580745386307547168512335551204731/invalid </PRE> <P></P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_url_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="xss_url_hint2" style="display:none"> <P> The most dangerous characters in a URL are <CODE><</CODE> and <CODE>></CODE>. If you can get an application to directly insert what you want in a page and can get those characters through, then you can probably get a script through. Try these: </P> <P></P> <PRE> https://google-gruyere.appspot.com/382665580745386307547168512335551204731/%3e%3c https://google-gruyere.appspot.com/382665580745386307547168512335551204731/%253e%253c https://google-gruyere.appspot.com/382665580745386307547168512335551204731/%c0%be%c0%bc https://google-gruyere.appspot.com/382665580745386307547168512335551204731/%26gt;%26lt; https://google-gruyere.appspot.com/382665580745386307547168512335551204731/%26amp;gt;%26amp;lt; https://google-gruyere.appspot.com/382665580745386307547168512335551204731/\074\x3c\u003c\x3C\u003C\X3C\U003C https://google-gruyere.appspot.com/382665580745386307547168512335551204731/+ADw-+AD4- </PRE> <P> This tries <CODE>></CODE> and <CODE><</CODE> in many different ways that might be able to make it through the URL and get rendered incorrectly using: verbatim (URL %-encoding), double %-encoding, bad UTF-8 encoding, HTML &-encoding, double &-encoding, and several different variations on C-style encoding. View the resulting source and see if any of those work. (Note: literally typing <CODE>><</CODE> in the URL is identical to <CODE>%3e%3c</CODE> because the browser automatically %-encodes those character. If you are trying to want a literal <CODE>></CODE> or <CODE><</CODE> then you will need to use a tool like curl to send those characters in URL.) </P> <P></P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_url_sol');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="xss_url_sol" style="display:none"> <P> <B>To exploit,</B> create a URL like the following and get a victim to click on it: </P> <P></P> <PRE> https://google-gruyere.appspot.com/382665580745386307547168512335551204731/<script>alert(1)</script> </PRE> <P> <B>To fix,</B> you need to escape user input that is displayed in error messages. Error messages are displayed using <CODE><A href="/code/?resources/error.gtl">error.gtl</A></CODE>, but are not escaped in the template. The part of the template that renders the message is <CODE>{{message}}</CODE> and it's missing the modifier that tells it to escape user input. Add the <CODE>:text</CODE> modifier to escape the user input: </P><PRE> <div class="message">{{_message:text}}</div> </PRE> <P> This flaw would have been best mitigated by a design that escapes all output by default and only displays raw HTML when explicitly tagged to do so. There are also <A href="https://www.google.com/search?q=XSS+auto+escaping" target="_top">autoescaping</A> features available in many template systems. </P> <!--MARK-2--> <P></P> </DIV> </DIV> <P></P> <H3><A name="2__stored_xss"> </A> Stored XSS </H3> <P> <IMG src="/static/cheese_b.png" style="vertical-align:middle; padding-right: 5px;"> <B>Now find a stored XSS. What we want to do is put a script in a place where Gruyere will serve it back to another user.</B> </P> The most obvious place that Gruyere serves back user-provided data is in a snippet (ignoring uploaded files which we've already discussed.)<P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_stored_hint1');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="xss_stored_hint1" style="display:none"> <P> Put this in a snippet and see what you get: </P> <P></P> <PRE> <script>alert(1)</script> </PRE> <P> There are many different ways that script can be embedded in a document. </P> <P></P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_stored_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="xss_stored_hint2" style="display:none"> <P> Hackers don't limit themselves to valid HTML syntax. Try some invalid HTML and see what you get. You may need to experiment a bit in order to find something that will work. There are multiple ways to do this. </P> <P></P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_stored_sol');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="xss_stored_sol" style="display:none"> <P> <B>To exploit,</B> enter any of these as your snippet (there are certainly more methods): </P> <P></P> <PRE> (1) <a onmouseover="alert(1)" href="#">read this!</a> (2) <p <script>alert(1)</script>hello (3) </td <script>alert(1)</script>hello </PRE> <P> Notice that there are multiple failures in sanitizing the HTML. Snippet 1 worked because <CODE>onmouseover</CODE> was inadvertently omitted from the list of disallowed attributes in <CODE><A href="/code/?sanitize.py">sanitize.py</A></CODE>. Snippets 2 and 3 work because browsers tend to be forgiving with HTML syntax and the handling of both start and end tags is buggy. </P> <P> <B>To fix,</B> we need to investigate and fix the sanitizing performed on the snippets. Snippets are sanitized in <CODE>_SanitizeTag</CODE> in the <CODE><A href="/code/?sanitize.py">sanitize.py</A></CODE> file. Let's block snippet 1 by adding <CODE>"onmouseover"</CODE> to the list of <CODE>disallowed_attributes</CODE>. </P> <P> <B>Oops!</B> This doesn't completely solve the problem. Looking at the code that was just fixed, can you find a way to bypass the fix? </P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_stored_not_fixed_hint');"><IMG src="/static/closed.gif"> Hint </H4> <DIV id="xss_stored_not_fixed_hint" style="display:none"> <P> Take a close look at the code in <CODE>_SanitizeTag</CODE> that determines whether or not an HTML attribute is allowed or not. </P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_stored_not_fixed');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="xss_stored_not_fixed" style="display:none"> <P> The fix was insufficient because the code that checks for disallowed attributes is case sensitive and HTML is not. So this still works: </P> <P></P> <PRE> (1') <a ONMOUSEOVER="alert(1)" href="#">read this!</a> </PRE> <P> Correctly sanitizing HTML is a tricky problem. The <CODE>_SanitizeTag</CODE> function has a number of critical design flaws: </P> <P></P> <UL> <LI> It does not validate the well-formedness of the input HTML. As we see, badly formed HTML passes through the sanitizer unchanged. Since browsers typically apply very lenient parsing, it is very hard to predict the browser's interpretation of the given HTML unless we exercise strict control on its format. </LI> <LI> It uses blacklisting of attributes, which is a bad technique. One of our exploits got past the blacklist simply by using an uppercase version of the attribute. There could be other attributes <A href="https://www.w3.org/TR/html40/index/attributes.html" target="_top">missing from this list</A> that are dangerous. It is always better to whitelist known good values. </LI> <LI> The sanitizer does not do any further sanitization of attribute values. This is dangerous since URI attributes like <CODE>href</CODE> and <CODE>src</CODE> and the <CODE>style</CODE> attribute can all be used to inject JavaScript. </LI> </UL> <P> The right approach to HTML sanitization is to: </P><UL> <LI> Parse the input into an intermediate DOM structure, then rebuild the body as well-formed output. </LI> <LI> Use strict whitelists for allowed tags and attributes. </LI> <LI> Apply strict sanitization of URL and CSS attributes if they are permitted. </LI> </UL> <P>Whenever possible it is preferable to use an already available known and proven <A href="https://www.google.com/search?q=sanitize+html" target="_top">HTML sanitizer</A>. </P> <!--MARK-3--> <P></P> </DIV> </DIV> </DIV> <P></P> <H3><A name="2__stored_xss_via_html_attribute"> </A> Stored XSS via HTML Attribute </H3> <P> <IMG src="/static/cheese_b.png" style="vertical-align:middle; padding-right: 5px;"> <B>You can also do XSS by injecting a value into an HTML attribute. Inject a script by setting the color value in a profile.</B> </P><DIV style="margin-left:18pt"> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_color_hint');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="xss_color_hint" style="display:none"> <P> The color is rendered as <CODE>style='color:<I>color</I>'</CODE>. Try including a single quote character in your color name. </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_color_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="xss_color_hint2" style="display:none"> <P> You can insert an HTML attribute that executes a script. </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_color_sol');"><IMG src="/static/closed.gif"> Exploit and Fixes </H4> <DIV id="xss_color_sol" style="display:none"> <P> <B>To exploit,</B> use the following for your color preference: </P> <P></P> <PRE> red' onload='alert(1)' onmouseover='alert(2) </PRE> <P> You may need to move the mouse over the snippet to trigger the attack. This attack works because the first quote ends the <CODE>style</CODE> attribute and the second quote starts the onload attribute. </P> <P> But this attack shouldn't work at all. Take a look at <CODE><A href="/code/?resources/home.gtl">home.gtl</A></CODE> where it renders the color. It says <CODE>style='{{color:text}}'</CODE> and as we saw earlier, the <CODE>:text</CODE> part tells it to escape text. So why doesn't this get escaped? In <CODE><A href="/code/?gtl.py">gtl.py</A></CODE>, it calls <CODE>cgi.escape(str(value))</CODE> which takes an optional second parameter that indicates that the value is being used in an HTML attribute. So you can replace this with <CODE>cgi.escape(str(value),True)</CODE>. Except that doesn't fix it! The problem is that <CODE>cgi.escape</CODE> assumes your HTML attributes are enclosed in double quotes and this file is using single quotes. (This should teach you to always carefully read the documentation for libraries you use and to always test that they do what you want.) </P> <P> You'll note that this attack uses both <CODE>onload</CODE> and <CODE>onmouseover</CODE>. That's because even though W3C specifies that onload events is only supported on <CODE>body</CODE> and <CODE>frameset</CODE> elements, some browsers support them on other elements. So if the victim is using one of those browsers, the attack always succeeds. Otherwise, it succeeds when the user moves the mouse. It's not uncommon for attackers to use multiple attack vectors at the same time. </P> <P> <B>To fix,</B> we need to use a correct text escaper, that escapes single and double quotes too. Add the following function to <CODE><A href="/code/?gtl.py">gtl.py</A></CODE> and call it instead of <CODE>cgi.escape</CODE> for the <CODE>text</CODE> escaper. </P> <P></P> <PRE> def _EscapeTextToHtml(var): """Escape HTML metacharacters. This function escapes characters that are dangerous to insert into HTML. It prevents XSS via quotes or script injected in attribute values. It is safer than cgi.escape, which escapes only <, >, & by default. cgi.escape can be told to escape double quotes, but it will never escape single quotes. """ meta_chars = { '"': '&quot;', '\'': '&#39;', # Not &apos; '&': '&amp;', '<': '&lt;', '>': '&gt;', } escaped_var = "" for i in var: if i in meta_chars: escaped_var = escaped_var + meta_chars[i] else: escaped_var = escaped_var + i return escaped_var </PRE> <P> <B>Oops!</B> This doesn't completely solve the problem. Even with the above fix in place, the color value is still vulnerable. <H4 style="cursor: pointer" onclick="toggleBlock(this, 'another_style_xss_hint1');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="another_style_xss_hint1" style="display:none"> <P> Some browsers allow you to include script in stylesheets. </P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'another_style_xss_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="another_style_xss_hint2" style="display:none"> <P> The easiest browser to exploit in this way is Internet Explorer which supports dynamic CSS properties. </P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'another_style_xss_sol');"><IMG src="/static/closed.gif"> Another Exploit and Fix </H4> <DIV id="another_style_xss_sol" style="display:none"> <P> Internet Explorer's dynamic CSS properites (aka CSS expressions) make this attack particularly easy. </P> <B>To exploit,</B> use the following for your color preference: <PRE> expression(alert(1)) </PRE> <P> While other browsers don't support CSS expressions, there are other dangerous CSS properties, such as Mozilla's <CODE>-moz-binding</CODE>. </P> <P> <B>To fix,</B> we need to sanitize the color as a color. The best thing to do would be to add a new output sanitizing form to gtl, i.e., we would write <CODE>{{foo:color}}</CODE> which makes sure <CODE>foo</CODE> is safe to use as a color. This function can be used to sanitize: </P> <PRE> SAFE_COLOR_RE = re.compile(r"^#?[a-zA-Z0-9]*$") def _SanitizeColor(color): """Sanitizes a color, returning 'invalid' if it's invalid. A valid value is either the name of a color or # followed by the hex code for a color (like #FEFFFF). Returning an invalid value value allows a style sheet to specify a default value by writing 'color:default; color:{{foo:color}}'. """ if SAFE_COLOR_RE.match(color): return color return 'invalid' </PRE> <P> Colors aren't the only values we might want to allow users to provide. You should do similar sanitizing for user-provided fonts, sizes, urls, etc. It's helpful to do input validation, so that when a user enters an invalid value, you'll reject it at that time. But only doing input validation would be a mistake: if you find an error in your validation code or a new browser exposes a new attack vector, you'd have to go back and scrub all previously entered values. Or, you could add the output validation which you should have been doing in the first place. </DIV> </P></DIV> </DIV> <P></P> <H3><A name="2__stored_xss_via_ajax"> </A> Stored XSS via AJAX </H3> <P> <IMG src="/static/cheese_b.png" style="vertical-align:middle; padding-right: 5px;"> <B>Find an XSS attack that uses a bug in Gruyere's AJAX code.</B> The attack should be triggered when you click the refresh link on the page. </P> <P></P> <DIV style="margin-left:18pt"> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_ajax_hint');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="xss_ajax_hint" style="display:none"> <P> Run <CODE>curl</CODE> on <CODE>https://google-gruyere.appspot.com/382665580745386307547168512335551204731/feed.gtl</CODE> and look at the result. (Or browse to it in your browser and view source.) You'll see that it includes each user's first snippet into the response. This entire response is then evaluated on the client side which then inserts the snippets into the document. Can you put something in your snippet that will be parsed differently than expected? </P> <P></P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_ajax_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="xss_ajax_hint2" style="display:none"> <P> Try putting some quotes (<CODE>"</CODE>) in your snippet. </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_ajax_sol');"><IMG src="/static/closed.gif"> Exploit and Fixes </H4> <DIV id="xss_ajax_sol" style="display:none"> <P> <B>To exploit,</B> Put this in your snippet: </P> <P></P> <PRE> all <span style=display:none>" + (alert(1),"") + "</span>your base </PRE> <P> The JSON should look like <PRE>_feed(({..., "Mallory": "snippet", ...}))</PRE> but instead looks like this: <PRE>_feed({..., "Mallory": <U>"all <span style=display:none>"</U> + <U>(alert(1),"")</U> + <U>"</span>your base"</U>, ...})</PRE> Each underlined part is a separate expression. Note that this exploit is written to be invisible both in the original page rendering (because of the <CODE><span style=display:none></CODE>) and after refresh (because it inserts only an empty string). All that will appear on the screen is <A href="https://www.google.com/search?q=all+your+base+are+belong+to+us" target="_top">all your base</A>. There are bugs on both the server and client sides which enable this attack. </P> <P> <B>To fix,</B> first, on the server side, the text is incorrectly escaped when it is rendered in the JSON response. The template says <CODE>{{snippet.0:html}}</CODE> but that's not enough. This text is going to be inserted into the innerHTML of a DOM node so the HTML does have to be sanitized. However, that sanitized text is then going to be inserted into JavaScript and single and double quotes have to be escaped. That is, adding support for <CODE>{{...:js}}</CODE> to GTL would not be sufficient; we would also need to support something like <CODE>{{...:html:js}}</CODE>. </P><P>To escape quotes, use <CODE>\x27</CODE> and <CODE>\x22</CODE> for single and double quote respectively. Replacing them with <CODE>&#27;</CODE> and <CODE>&quot;</CODE> is incorrect as those are not recognized in JavaScript strings and will break quotes around HTML attribute. </P> <P> Second, in the browser, Gruyere converts the JSON by using JavaScript's <CODE>eval</CODE>. In general, <CODE>eval</CODE> is very dangerous and should rarely be used. If it used, it must be used very carefully, which is hardly the case here. We should be using the JSON parser which ensures that the string does not include any unsafe content. The JSON parser is available at <A href="http://www.json.org/" target="_top">json.org</A>. </P> <P></P> </DIV> </DIV> <P></P> <H3><A name="2__reflected_xss_via_ajax"> </A> Reflected XSS via AJAX </H3> <P> <IMG src="/static/cheese_b.png" style="vertical-align:middle; padding-right: 5px;"><B>Find a URL that when clicked on will execute a script using one of Gruyere's AJAX features.</B> </P> <P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_ajax2_hint');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="xss_ajax2_hint" style="display:none"> <P> When Gruyere refreshes a user snippets page, it uses <PRE>https://google-gruyere.appspot.com/382665580745386307547168512335551204731/feed.gtl?uid=value</PRE> and the result is the script <PRE>_feed((["user", "snippet1", ... ]))</PRE> </P> <P></P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_ajax2_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="xss_ajax2_hint2" style="display:none"> <P> This uses a different vulnerability, but the exploit is very similar to the previous reflected XSS exploit. </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_ajax2_sol');"><IMG src="/static/closed.gif"> Exploit and Fixes </H4> <DIV id="xss_ajax2_sol" style="display:none"> <P> <B>To exploit,</B> create a URL like the following and get a victim to click on it: </P> <P></P> <PRE> https://google-gruyere.appspot.com/382665580745386307547168512335551204731/feed.gtl?uid=<script>alert(1)</script> https://google-gruyere.appspot.com/382665580745386307547168512335551204731/feed.gtl?uid=%3Cscript%3Ealert(1)%3C/script%3E </PRE> <P> This renders as <PRE>_feed((["<script>alert(1)</script>"]))</PRE> which surprisingly <I>does</I> execute the script. The bug is that Gruyere returns all gtl files as content type <CODE>text/html</CODE> and browsers are very tolerant of what HTML files they accept. </P> <P> <B>To fix,</B> you need to make sure that your JSON content can never be interpreted as HTML. Even though literal <CODE><</CODE> and <CODE>></CODE> are allowed in JavaScript strings, you need to make sure they don't appear literally where a browser can misinterpret them. Thus, you'd need to modify <CODE>{{...:js}}</CODE> to replace them with the JavaScript escapes <CODE>\x3c</CODE> and <CODE>\x3e</CODE>. It is always safe to write <CODE>'\x3c\x3e'</CODE> in Javscript strings instead of <CODE>'<>'</CODE>. (And, as noted above, using the HTML escapes <CODE>&lt;</CODE> and <CODE>&gt;</CODE> is incorrect.) </P> <P> You should also always set the content type of your responses, in this case serving JSON results as <CODE>application/javascript.</CODE> This alone doesn't solve the problem because browsers don't always respect the content type: browsers sometimes do "sniffing" to try to "fix" results from servers that don't provide the correct content type. </P> <P><B>But wait, there's more!</B> Gruyere doesn't set the content encoding either. And some browsers try to guess what the encoding type of a document is or an attacker may be able to embed content in a document that defines the content type. So, for example, if an attacker can trick the browser into thinking a document is <CODE><A href="https://www.google.com/search?q=utf-7">UTF-7</A></CODE> then it could embed a script tag as <CODE>+ADw-script+AD4-</CODE> since <CODE>+ADw-</CODE> and <CODE>+AD4-</CODE> are alternate encodings for <CODE><</CODE> and <CODE>></CODE>. So always set both the content type <I>and</I> the content encoding of your responses, e.g., for HTML:</P> <PRE> Content-Type: text/html; charset=utf-8 </PRE> </DIV> </DIV> <H3><A name="2__more_about_xss"> </A> More about XSS </H3> <P> In addition to the XSS attacks described above, there are quite a few more ways to attack Gruyere with XSS. Collect them all! </P> <P> XSS is a difficult beast. On one hand, a fix to an XSS vulnerability is usually trivial and involves applying the correct sanitizing function to user input when it's displayed in a certain context. On the other hand, if history is any indication, this is extremely difficult to get right. <A href="https://www.kb.cert.org/vuls/" target="_top">US-CERT</A> reports dozens of publicly disclosed XSS vulnerabilities involving multiple companies. </P> <P> Though there is no magic defense to getting rid of XSS vulnerabilities, here are some steps you should take to prevent these types of bugs from popping up in your products: </P> <P></P> <OL> <LI> First, make sure you <A href="https://www.google.com/search?q=understanding+cross-site+scripting" target="_top">understand the problem</A>. </LI> <LI> Wherever possible, do sanitizing via templates features instead of calling escaping functions in source code. This way, all of your escaping is done in one place and your product can benefit from security technologies designed for template systems that verify their correctness or actually do the escaping for you. Also, familiarize yourself with the other security features of your template system. </LI> <LI> Employ good testing practices with respect to XSS. </LI> <LI> Don't write your own template library :) </LI> </OL> <!--MARK-6--> <BR><P></P> <FONT SIZE="+2"> <A href="/part3">Continue >></A> </FONT><BR> <BR> <P style="font-size:x-small"> © Google 2017 <A href="https://www.google.com/intl/en/policies/terms/">Terms of Service</A> <BR>The code portions of this codelab are licensed under the Creative Commons Attribution-No Derivative Works 3.0 United States license <<A href="https://creativecommons.org/licenses/by-nd/3.0/us/">https://creativecommons.org/licenses/by-nd/3.0/us</A>>. Brief excerpts of the code may be used for educational or instructional purposes provided this notice is kept intact. Except as otherwise noted the remainder of this codelab is licensed under the Creative Commons Attribution 3.0 United States license <<A href="https://creativecommons.org/licenses/by/3.0/us/">https://creativecommons.org/licenses/by/3.0/us</A>>. </P> </BODY></HTML> |
| URL | https://google-gruyere.appspot.com/part3 |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 365 bytes. |
GET https://google-gruyere.appspot.com/part3 HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: https://google-gruyere.appspot.com/ Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 301 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: f4439e90565e10881aa7a1deee88e28a Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 29253 Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 |
| Response Body - size: 29,253 bytes. |
<HTML><HEAD><META http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<TITLE>Web Application Exploits and Defenses</TITLE> <LINK type="text/css" rel="stylesheet" href="../static/codelab.css"> <!--MARK-Z--> <SCRIPT> function toggleBlock(heading, whichID) { var image = heading.childNodes[0]; var block = document.getElementById(whichID); if (block) { if (getDisplay(block) == 'block') { block.style.display = 'none'; image.src = 'static/closed.gif'; } else { // "none" or "" block.style.display = 'block'; image.src = 'static/open.gif'; } } } function getDisplay(block) { var value = block.style.display; if (!value) { if (document.defaultView) { var computedStyle = document.defaultView.getComputedStyle(block, ""); value = computedStyle.getPropertyValue('display'); } else if (block.currentStyle) { value = block.currentStyle.display; } } return value; } </SCRIPT> </HEAD> <BODY bgcolor="#ffffff"> <DIV class="banner"></DIV> <H1><FONT size="+2"><IMG src="/static/gruyere-78.png" style="vertical-align:middle"> Web Application Exploits and Defenses (Part 3)</FONT> </H1> <P> A Codelab by Bruce Leban, Mugdha Bendre, and Parisa Tabriz </P> <DIV class="printable"> <DIV class="column1"> Table of Contents <UL> <LI class="L1" style="padding-top:0pt"> <A href="/#0__hackers">Beat the hackers</A></LI> <LI class="L1" style="padding-top:0pt"> <A href="/#0__gruyere">Gruyere</A></LI> <LI class="L1"> <A href="/part1#1__setup">Set-up</A> <UL> <LI class="L2"> <A href="/part1#1__reset_button">Reset Button</A></LI> <LI class="L2"> <A href="/part1#1__about_the_code">About the Code</A></LI> <LI class="L2"> <A href="/part1#1__features_and_technologies">Features and Technologies</A></LI> </UL></LI> <LI class="L1"> <A href="/part1#1__using_gruyere">Using Gruyere</A></LI> <LI class="L1"> <A href="/part2#2__cross_site_scripting">Cross-Site Scripting (XSS)</A> <UL> <LI class="L2"> <A href="/part2#2__xss_challenge">XSS Challenges</A></LI> <LI class="L2"> <A href="/part2#2__file_upload_xss">File Upload XSS</A></LI> <LI class="L2"> <A href="/part2#2__reflected_xss">Reflected XSS</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss">Stored XSS</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss_via_html_attribute">Stored XSS via HTML Attribute</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss_via_ajax">Stored XSS via AJAX</A></LI> <LI class="L2"> <A href="/part2#2__reflected_xss_via_ajax">Reflected XSS via AJAX</A></LI> <LI class="L2"> <A href="/part2#2__more_about_xss">More about XSS</A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__client_state_manipulation">Client-State Manipulation</A> <UL> <LI class="L2"> <A href="/part3#3__elevation_of_privilege"> Elevation of Privilege </A></LI> <LI class="L2"> <A href="/part3#3__cookie_manipulation"> Cookie Manipulation </A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__cross_site_request_forgery">Cross-Site Request Forgery (XSRF)</A> <UL> <LI class="L2"> <A href="/part3#3__xsrf_challenge">XSRF Challenge</A></LI> <LI class="L2"> <A href="/part3#3__more_about_preventing_xsrf">More about preventing XSRF</A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__cross_site_script_inclusion">Cross Site Script Inclusion (XSSI)</A> <UL> <LI class="L2"> <A href="/part3#3__xssi_challenge">XSSI Challenge</A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__path_traversal">Path Traversal</A> <UL> <LI class="L2"> <A href="/part4#4__information_disclosure_path_traversal">Information disclosure via path traversal </A></LI> <LI class="L2"> <A href="/part4#4__data_tampering_path_traversal">Data tampering via path traversal </A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__denial_of_service">Denial of Service</A> <UL> <LI class="L2"> <A href="/part4#4__dos_quit_server">DoS - Quit the Server</A></LI> <LI class="L2"> <A href="/part4#4__dos_overload_server">DoS - Overloading the Server</A></LI> <LI class="L2"> <A href="/part4#4__more_dos">More on Denial of Service</A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__code_execution">Code Execution</A> <UL> <LI class="L2"> <A href="/part4#4__code_execution_challenge">Code Execution Challenge</A></LI> <LI class="L2"> <A href="/part4#4__more_code_execution">More on Remote Code Execution</A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__configuration_vulnerabilities">Configuration Vulnerabilities</A> <UL> <LI class="L2"> <A href="/part5#5__information_disclosure_config_1">Information disclosure #1</A></LI> <LI class="L2"> <A href="/part5#5__information_disclosure_config_2">Information disclosure #2</A></LI> <LI class="L2"> <A href="/part5#5__information_disclosure_bug_3">Information disclosure #3 </A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__ajax_vulnerabilities">AJAX vulnerabilities</A> <UL> <LI class="L2"> <A href="/part5#5__dos_via_ajax">DoS via AJAX</A></LI> <LI class="L2"> <A href="/part5#5__phishing_via_ajax">Phishing via AJAX</A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__other_vulnerabilities"> Other Vulnerabilities </A> </H2> <UL> <LI class="L2"> <A href="/part5#5__buffer_and_integer_overflow">Buffer Overflow and Integer Overflow</A></LI> <LI class="L2"> <A href="/part5#5__sql_injection"> SQL Injection </A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__after_the_codelab">After the Codelab</A></LI> </UL> </DIV> <DIV class="column2"> <P></P> <P></P> <P> <NOAUTOLINK> </NOAUTOLINK></P> <H2><A name="3__client_state_manipulation"> </A> Client-State Manipulation </H2> <P> When a user interacts with a web application, they do it indirectly through a browser. When the user clicks a button or submits a form, the browser sends a request back to the web server. Because the browser runs on a machine that can be controlled by an attacker, the application must not trust any data sent by the browser. </P><P> It might seem that not trusting any user data would make it impossible to write a web application but that's not the case. If the user submits a form that says they wish to purchase an item, it's OK to trust that data. But if the submitted form also includes the price of the item, that's something that cannot be trusted. </P> <P></P> <H3><A name="3__elevation_of_privilege"> </A> Elevation of Privilege </H3> <IMG src="/static/cheese_w.png" style="vertical-align:middle; padding-right: 5px;"> <B>Convert your account to an administrator account.</B> <P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'elevation_hint');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="elevation_hint" style="display:none"> <P> Take a look at the <CODE><A href="/code/?resources/editprofile.gtl">editprofile.gtl</A></CODE> page that users and administrators use to edit profile settings. If you're not an administrator, the page looks a bit different. Can you figure out how to fool Gruyere into letting you use this page to update your account? </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'elevation_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="elevation_hint2" style="display:none"> <P> Can you figure out how to fool Gruyere into <i>thinking</i> you used this page to update your account? </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'clientstate');"><IMG src="/static/closed.gif"> Exploit and Fixes </H4> <DIV id="clientstate" style="display:none"> <P> You can convert your account to being an administrator by issuing either of the following requests: </P><UL> <LI> <CODE>https://google-gruyere.appspot.com/382665580745386307547168512335551204731/saveprofile?action=update&is_admin=True</CODE> </LI> <LI> <CODE>https://google-gruyere.appspot.com/382665580745386307547168512335551204731/saveprofile?action=update&is_admin=True&uid=username</CODE> (which will make any <CODE>username</CODE> into an an admin) </LI> </UL> <P>After visiting this URL, your account is now marked as an administrator but your cookie still says you're not. So sign out and back in to get a new cookie. After logging in, notice the 'Manage this server' link on the top right.</P> <P>The bug here is that there is no validation on the server side that the request is authorized. The only part of the code that restricts the changes that a user is allowed to make are in the template, hiding parts of the UI that they shouldn't have access to. The correct thing to do is to check for authorization on the server, at the time that the request is received. </P> </DIV> </DIV> <P></P> <H3><A name="3__cookie_manipulation"> </A> Cookie Manipulation </H3> Because the HTTP protocol is stateless, there's no way a web server can automatically know that two requests are from the same user. For this reason, <A href="https://www.google.com/search?q=http+cookies">cookies</a> were invented. When a web site includes a cookie (an arbitrary string) in a HTTP response, the browser automatically sends the cookie back to the browser on the next request. Web sites can use the cookie to save session state. Gruyere uses cookies to remember the identity of the logged in user. Since the cookie is stored on the client side, it's vulnerable to manipulation. Gruyere protects the cookies from manipulation by adding a hash to it. Notwithstanding the fact that this hash isn't very good protection, you don't need to break the hash to execute an attack. </P><P> <IMG src="/static/cheese_bw.png" style="vertical-align:middle; padding-right: 5px;"> <B>Get Gruyere to issue you a cookie for someone else's account.</B> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'cookie_hint');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="cookie_hint" style="display:none"> <P> You don't need to look at the Gruyere cookie parsing code. You just need to know what the cookies look like. Gruyere's cookies use the format: <PRE> <i>hash</i>|<i>username</i>|admin|author </PRE> </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'cookie_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="cookie_hint2" style="display:none"> <P> Gruyere issues a cookie when you log in. Can you trick it into issuing you a cookie that looks like another user's cookie? </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'cookieparsing');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="cookieparsing" style="display:none"> <P> You can get Gruyere to issue you a cookie for someone else's account by creating a new account with username <CODE>"foo|admin|author"</CODE>. When you log into this account, it will issue you the cookie <CODE>"hash|foo|admin|author||author"</CODE> which actually logs you into <CODE>foo</CODE> as an administrator. (So this is also an elevation of privilege attack.) </P> <P>Having no restrictions on the characters allowed in usernames means that we have to be careful when we handle them. In this case, the cookie parsing code is tolerant of malformed cookies and it shouldn't be. It should escape the username when it constructs the cookie and it should reject a cookie if it doesn't match the exact pattern it is expecting. </P> <P>Even if we fix this, Python's hash function is not cryptographically secure. If you look at Python's <CODE>string_hash</CODE> function in <CODE><a href="https://svn.python.org/projects/python/trunk/Objects/stringobject.c">python/Objects/stringobject.cc</A></CODE> you'll see that it hashes the string strictly from left to right. That means that we don't need to know the cookie secret to generate our own hashes; all we need is another string that hashes to the same value, which we can find in a relatively short time on a typical PC. In contrast, with a cryptographic hash function, changing any bit of the string will change many bits of the hash value in an unpredictable way. At a minimum, you should use a secure hash function to protect your cookies. You should also consider encrypting the entire cookie as plain text cookies can expose information you might not want exposed. </P> <P>And these cookies are also vulnerable to a replay attack. Once a user is issued a cookie, it's good forever and there's no way to revoke it. So if a user is an administrator at one time, they can save the cookie and continue to act as an administrator even if their administrative rights are taken away. While it's convenient to not have to make a database query in order to check whether or not a user is an administrator, that might be too dangerous a detail to store in the cookie. If avoiding additional database access is important, the server could cache a list of recent admin users. Including a timestamp in a cookie and expiring it after some period of time also mitigates against a replay attack.</P> <P><B>Another challenge:</B> Since account names are limited to 16 characters, it seems that this trick would not work to log in to the actual <CODE>administrator</CODE> account since <CODE>"administrator|admin"</CODE> is 19 characters. Can you figure out how to bypass that restriction? </P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'cookie2');"><IMG src="/static/closed.gif"> Additional Exploit and Fix </H4> <DIV id="cookie2" style="display:none"> The 16 character limit is implemented on the client side. Just issue your own request: <PRE> https://google-gruyere.appspot.com/382665580745386307547168512335551204731/saveprofile?action=new&uid=administrator|admin|author&pw=secret </PRE> <P>Again, this restriction should be implemented on the server side, not just the client side.</P> </DIV> </DIV> </DIV> <BR><BR> <H2><A name="3__cross_site_request_forgery"> </A> Cross-Site Request Forgery (XSRF) </H2> <P> The previous section said "If the user submits a form that says they wish to purchase an item, it's OK to trust that data." That's true as long as it really was the user that submitted the form. If your site is vulnerable to XSS, then the attacker can fake any request as if it came from the user. But even if you've protected against XSS, there's another attack that you need to protect against: cross-site request forgery. </P><P> When a browser makes requests to a site, it always sends along any cookies it has for that site, regardless of where the request comes from. Additionally, web servers generally cannot distinguish between a request initiated by a deliberate user action (e.g., user clicking on "Submit" button) versus a request made by the browser without user action (e.g., request for an embedded image in a page). Therefore, if a site receives a request to perform some action (like deleting a mail, changing contact address), it cannot know whether this action was knowingly initiated by the user — even if the request contains authentication cookies. An attacker can use this fact to fool the server into performing actions the user did not intend to perform. </P> <P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xsrf_desc');"><IMG src="/static/closed.gif"> More details </H4> <DIV id="xsrf_desc" style="display:none"> <P> For example, suppose Blogger is vulnerable to XSRF attacks (it isn't). And let us say Blogger has a Delete Blog button on the dashboard that points to this URL: <PRE> https://www.blogger.com/deleteblog.do?blogId=BLOGID </PRE> Bob, the attacker, embeds the following HTML on his web page on <CODE>https://www.evil.example.com</CODE>: </P> <PRE> <img src="https://www.blogger.com/deleteblog.do?blogId=alice's-blog-id" style="display:none"> </PRE> <P> If the victim, Alice, is logged in to <CODE>www.blogger.com</CODE> when she views the above page, here is what happens: </P> <P></P> <UL> <LI> Her browser loads the page from <CODE>https://www.evil.example.com</CODE>. The browser then tries to load all embedded objects in the page, including the <CODE>img</CODE> shown above. </LI> <LI> The browser makes a request to <CODE>https://www.blogger.com/deleteblog.do?blogId=alice's-blog-id</CODE> to load the image. Since Alice is logged into Blogger — that is, she has a Blogger cookie — the browser also sends that cookie in the request. </LI> <LI> Blogger verifies the cookie is a valid session cookie for Alice. It verifies that the blog referenced by <CODE>alice's-blog-id</CODE> is owned by Alice. It deletes Alice's blog. </LI> <LI> Alice has no idea what hit her. </LI> </UL> <P> In this sample attack, since each user has their own blog id, the attack has to be specifically targeted to a single person. In many cases, though, requests like these don't contain any user-specific data. </P></DIV> </DIV> <P></P> <H3><A name="3__xsrf_challenge"> </A> XSRF Challenge </H3> <P> The goal here is to find a way to perform an account changing action on behalf of a logged in Gruyere user without their knowledge. Assume you can get them to visit a web page under your control. </P> <P> <IMG src="/static/cheese_b.png" style="vertical-align:middle; padding-right: 5px;"> <B> Find a way to get someone to delete one of their Gruyere snippets.</B> </P> <P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xsrf_hint');"><IMG src="/static/closed.gif"> Hint </H4> <DIV id="xsrf_hint" style="display:none"> <P> What is the URL used to delete a snippet? Look at the URL associated with the "X" next to a snippet. </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xsrf_soln');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="xsrf_soln" style="display:none"> <P> <B>To exploit,</B> lure a user to visit a page that makes the following request: </P> <P></P> <PRE> https://google-gruyere.appspot.com/382665580745386307547168512335551204731/deletesnippet?index=0 </PRE> <P> To be especially sneaky, you could set your Gruyere icon to this URL and the victim would be exploited when they visited the main page. </P> <P> <B>To fix,</B> we should first change <CODE>/deletesnippet</CODE> to work via a <CODE>POST</CODE> request since this is a state changing action. In the HTML form, change <CODE>method='get'</CODE> to <CODE>method='post'</CODE>. On the server side, <CODE>GET</CODE> and <CODE>POST</CODE> requests look the same except that they usually call different handlers. For example, Gruyere uses Python's BaseHTTPServer which calls <CODE>do_GET</CODE> for <CODE>GET</CODE> requests and <CODE>do_POST</CODE> for <CODE>POST</CODE> requests. </P><P> <B>However</B>, note that changing to <CODE>POST</CODE> is not enough of a fix in itself! (Gruyere uses <CODE>GET</CODE> requests exclusively because it makes hacking it a bit easier. <CODE>POST</CODE> is not more secure than <CODE>GET</CODE> but it is more correct: browsers may re-issue <CODE>GET</CODE> requests which can result in an action getting executed more than once; browsers won't reissue <CODE>POST</CODE> requests without user consent.) Then we need to pass a unique, unpredictable authorization token to the user and require that it get sent back before performing the action. For this authorization token, <CODE>action_token</CODE>, we can use a hash of the value of the user's cookie appended to a current timestamp and include this token in all state-changing HTTP requests as an additional HTTP parameter. The reason we use <CODE>POST</CODE> over <CODE>GET</CODE> requests is that if we pass <CODE>action_token</CODE> as a URL parameter, it might leak via HTTP Referer headers. The reason we include the timestamp in our hash is so that we can expire old tokens, which mitigates the risk if it leaks. </P> <P> When a request is processed, Gruyere should regenerate the token and compare it with the value supplied with the request. If the values are equal, then it should perform the action. Otherwise, it should reject it. The functions that generate and verify the tokens look like this: </P> <P></P> <PRE> def _GenerateXsrfToken(self, cookie): """Generates a timestamp and XSRF token for all state changing actions.""" timestamp = time.time() return timestamp + "|" + (str(hash(cookie_secret + cookie + timestamp))) def _VerifyXsrfToken(self, cookie, action_token): """Verifies an XSRF token included in a request.""" # First, make sure that the token isn't more than a day old. (action_time, action_hash) = action_token.split("|", 1) now = time.time() if now - 86400 > float(action_time): return False # Second, regenerate it and check that it matches the user supplied value hash_to_verify = str(hash(cookie_secret + cookie + action_time) return action_hash == hash_to_verify </PRE> <P> <B>Oops!</B> There's several things wrong with these functions. </P><H4 style="cursor: pointer" onclick="toggleBlock(this, 'xsrf_whats_wrong');"><IMG src="/static/closed.gif"> What's missing? </H4> <DIV id="xsrf_whats_wrong" style="display:none"> <P> By including the time in the token, we prevent it from being used forever, but if an attacker were to gain access to a copy of the token, they could reuse it as many times as they wanted within that 24 hour period. The expiration time of a token should be set to a small value that represents the reasonable length of time it will take the user to make a request. This token also doesn't protect against an attack where a token for one request is intercepted and then used for a different request. As suggested by the name <CODE>action_token</CODE>, the token should be tied to the specific state changing action being performed, such as the URL of the page. A better signature for <CODE>_GenerateXsrfToken</CODE> would be <CODE>(self, cookie, action)</CODE>. For very long actions, like editing snippets, a script on the page could query the server to update the token when the user hits submit. (But read the next section about XSSI to make sure that an attacker won't be able to read that new token.)</P> <P> XSRF vulnerabilities exist because an attacker can easily script a series of requests to an application and than force a user to execute them by visiting some page. To prevent this type of attack, you need to introduce some value that can't be predicted or scripted by an attacker for <B>every account changing</B> request. Some application frameworks have XSRF protection built in: they automatically include a unique token in every response and verify it on every POST request. Other frameworks provide functions that you can use to do that. If neither of these cases apply, then you'll have to <A href="https://www.google.com/search?q=preventing+(XSRF+OR+CSRF)" target="_top">build your own</A>. Be careful of things that don't work: using <CODE>POST</CODE> instead of <CODE>GET</CODE> is advisable but not sufficient by itself, checking Referer headers is insufficient, and copying cookies into hidden form fields can make your cookies less secure. </P> <!--MARK-7--> </DIV> </DIV> <BR><P></P> </DIV> <H2><A name="3__cross_site_script_inclusion"> </A> Cross Site Script Inclusion (XSSI) </H2> <P> Browsers prevent pages of one domain from reading pages in other domains. But they do not prevent pages of a domain from referencing resources in other domains. In particular, they allow images to be rendered from other domains and scripts to be executed from other domains. An included script doesn't have its own security context. It runs in the security context of the page that included it. For example, if <CODE>www.evil.example.com</CODE> includes a script hosted on <CODE>www.google.com</CODE> then that script runs in the <CODE>evil</CODE> context not in the <CODE>google</CODE> context. So any user data in that script will "leak." </P> <!--MARK-8--> <P></P> <H3><A name="3__xssi_challenge"> </A> XSSI Challenge </H3> <P> <IMG src="/static/cheese_w.png" style="vertical-align:middle; padding-right: 5px;"> <B>Find a way to read someone else's private snippet using XSSI.</B> </P> <P> That is, create a page on another web site and put something in that page that can read your private snippet. (You don't need to post it to a web site: you can just create a <CODE>.html</CODE> in your home directory and double click on it to open in a browser.) </P> <P></P> <DIV style="margin-left:18pt"> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xssi_hint1');"><IMG src="/static/closed.gif"> Hint 1 </H4> <P></P> <DIV id="xssi_hint1" style="display:none"> <P> You can run a script from another domain by adding <PRE> <SCRIPT src="https://google-gruyere.appspot.com/382665580745386307547168512335551204731/..."></SCRIPT> </PRE> to your HTML file. What scripts does Gruyere have? </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xssi_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="xssi_hint2" style="display:none"> <P> <CODE><A href="/code/?resources/feed.gtl">feed.gtl</A></CODE> is a script. Given that, how can you get the private snippet out of the script? </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xssi_sol');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="xssi_sol" style="display:none"> <P> <B>To exploit,</B> put this in an html file: </P> <P></P> <PRE> <script> function _feed(s) { alert("Your private snippet is: " + s['private_snippet']); } </script> <script src="https://google-gruyere.appspot.com/382665580745386307547168512335551204731/feed.gtl"></script> </PRE> <P> When the script in <CODE><A href="/code/?resources/feed.gtl">feed.gtl</A></CODE> is executed, it runs in the context of the attacker's web page and uses the <CODE>_feed</CODE> function which can do whatever it wants with the data, including sending it off to another web site. </P> <P> You might think that you can fix this by eliminating the function call and just having the bare expression. That way, when the script is executed by inclusion, the response will be evaluated and then discarded. That won't work because JavaScript allows you to do things like redefine default constructors. So when the object is evaluated, the hosting page's constructors are invoked, which can do whatever they want with the values. </P> <!--MARK-9--> <P> <B>To fix,</B> there are several changes you can make. Any one of these changes will prevent currently possible attacks, but if you add several layers of protection ("<a href="https://www.google.com/search?q=%22defense+in+depth%22+security">defense in depth</a>") you protect against the possibility that you get one of the protections wrong and also against future browser vulnerabilities. First, use an XSRF token as discussed earlier to make sure that JSON results containing confidential data are only returned to your own pages. Second, your JSON response pages should only support <CODE>POST</CODE> requests, which prevents the script from being loaded via a script tag. Third, you should make sure that the script is not executable. The standard way of doing this is to append some non-executable prefix to it, like <CODE>])}while(1);</x></CODE>. A script running in the same domain can read the contents of the response and strip out the prefix, but scripts running in other domains can't. </P> <P> NOTE: Making the script not executable is more subtle than it seems. It's possible that what makes a script executable may change in the future if new scripting features or languages are introduced. Some people suggest that you can protect the script by making it a comment by surrounding it with <CODE>/*</CODE> and <CODE>*/</CODE>, but that's not as simple as it might seem. (Hint: what if someone included <CODE>*/</CODE> in one of their snippets?) </P> <P> There's <A href="https://www.google.com/search?q=%22cross+site+script+inclusion%22" target="_top">much more to XSSI</A> than this. There's a variation of JSON called JSONP which you should avoid using because it allows script injection <I>by design</I>. And there's <A href="https://www.google.com/search?q=E4X+markup+security" target="_top">E4X</A> (Ecmascript for XML) which can result in your HTML file being parsed as a script. Surprisingly, one way to protect against E4X attacks is to put some invalid XML in your files, like the <CODE></x></CODE> above. </P> <P></P> </DIV> </DIV> <BR><P></P> <FONT SIZE="+2"> <A href="/part4">Continue >></A> </FONT><BR> <BR> <P style="font-size:x-small"> © Google 2017 <A href="https://www.google.com/intl/en/policies/terms/">Terms of Service</A> <BR>The code portions of this codelab are licensed under the Creative Commons Attribution-No Derivative Works 3.0 United States license <<A href="https://creativecommons.org/licenses/by-nd/3.0/us/">https://creativecommons.org/licenses/by-nd/3.0/us</A>>. Brief excerpts of the code may be used for educational or instructional purposes provided this notice is kept intact. Except as otherwise noted the remainder of this codelab is licensed under the Creative Commons Attribution 3.0 United States license <<A href="https://creativecommons.org/licenses/by/3.0/us/">https://creativecommons.org/licenses/by/3.0/us</A>>. </P> </BODY></HTML> |
| URL | https://google-gruyere.appspot.com/part4 |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 365 bytes. |
GET https://google-gruyere.appspot.com/part4 HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: https://google-gruyere.appspot.com/ Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 301 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: 63bda5a5dfca824c605721a0d3f9018f Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 24149 Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 |
| Response Body - size: 24,149 bytes. |
<HTML><HEAD><META http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<TITLE>Web Application Exploits and Defenses</TITLE> <LINK type="text/css" rel="stylesheet" href="../static/codelab.css"> <!--MARK-Z--> <SCRIPT> function toggleBlock(heading, whichID) { var image = heading.childNodes[0]; var block = document.getElementById(whichID); if (block) { if (getDisplay(block) == 'block') { block.style.display = 'none'; image.src = 'static/closed.gif'; } else { // "none" or "" block.style.display = 'block'; image.src = 'static/open.gif'; } } } function getDisplay(block) { var value = block.style.display; if (!value) { if (document.defaultView) { var computedStyle = document.defaultView.getComputedStyle(block, ""); value = computedStyle.getPropertyValue('display'); } else if (block.currentStyle) { value = block.currentStyle.display; } } return value; } </SCRIPT> </HEAD> <BODY bgcolor="#ffffff"> <DIV class="banner"></DIV> <H1><FONT size="+2"><IMG src="/static/gruyere-78.png" style="vertical-align:middle"> Web Application Exploits and Defenses (Part 4)</FONT> </H1> <P> A Codelab by Bruce Leban, Mugdha Bendre, and Parisa Tabriz </P> <DIV class="printable"> <DIV class="column1"> Table of Contents <UL> <LI class="L1" style="padding-top:0pt"> <A href="/#0__hackers">Beat the hackers</A></LI> <LI class="L1" style="padding-top:0pt"> <A href="/#0__gruyere">Gruyere</A></LI> <LI class="L1"> <A href="/part1#1__setup">Set-up</A> <UL> <LI class="L2"> <A href="/part1#1__reset_button">Reset Button</A></LI> <LI class="L2"> <A href="/part1#1__about_the_code">About the Code</A></LI> <LI class="L2"> <A href="/part1#1__features_and_technologies">Features and Technologies</A></LI> </UL></LI> <LI class="L1"> <A href="/part1#1__using_gruyere">Using Gruyere</A></LI> <LI class="L1"> <A href="/part2#2__cross_site_scripting">Cross-Site Scripting (XSS)</A> <UL> <LI class="L2"> <A href="/part2#2__xss_challenge">XSS Challenges</A></LI> <LI class="L2"> <A href="/part2#2__file_upload_xss">File Upload XSS</A></LI> <LI class="L2"> <A href="/part2#2__reflected_xss">Reflected XSS</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss">Stored XSS</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss_via_html_attribute">Stored XSS via HTML Attribute</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss_via_ajax">Stored XSS via AJAX</A></LI> <LI class="L2"> <A href="/part2#2__reflected_xss_via_ajax">Reflected XSS via AJAX</A></LI> <LI class="L2"> <A href="/part2#2__more_about_xss">More about XSS</A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__client_state_manipulation">Client-State Manipulation</A> <UL> <LI class="L2"> <A href="/part3#3__elevation_of_privilege"> Elevation of Privilege </A></LI> <LI class="L2"> <A href="/part3#3__cookie_manipulation"> Cookie Manipulation </A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__cross_site_request_forgery">Cross-Site Request Forgery (XSRF)</A> <UL> <LI class="L2"> <A href="/part3#3__xsrf_challenge">XSRF Challenge</A></LI> <LI class="L2"> <A href="/part3#3__more_about_preventing_xsrf">More about preventing XSRF</A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__cross_site_script_inclusion">Cross Site Script Inclusion (XSSI)</A> <UL> <LI class="L2"> <A href="/part3#3__xssi_challenge">XSSI Challenge</A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__path_traversal">Path Traversal</A> <UL> <LI class="L2"> <A href="/part4#4__information_disclosure_path_traversal">Information disclosure via path traversal </A></LI> <LI class="L2"> <A href="/part4#4__data_tampering_path_traversal">Data tampering via path traversal </A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__denial_of_service">Denial of Service</A> <UL> <LI class="L2"> <A href="/part4#4__dos_quit_server">DoS - Quit the Server</A></LI> <LI class="L2"> <A href="/part4#4__dos_overload_server">DoS - Overloading the Server</A></LI> <LI class="L2"> <A href="/part4#4__more_dos">More on Denial of Service</A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__code_execution">Code Execution</A> <UL> <LI class="L2"> <A href="/part4#4__code_execution_challenge">Code Execution Challenge</A></LI> <LI class="L2"> <A href="/part4#4__more_code_execution">More on Remote Code Execution</A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__configuration_vulnerabilities">Configuration Vulnerabilities</A> <UL> <LI class="L2"> <A href="/part5#5__information_disclosure_config_1">Information disclosure #1</A></LI> <LI class="L2"> <A href="/part5#5__information_disclosure_config_2">Information disclosure #2</A></LI> <LI class="L2"> <A href="/part5#5__information_disclosure_bug_3">Information disclosure #3 </A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__ajax_vulnerabilities">AJAX vulnerabilities</A> <UL> <LI class="L2"> <A href="/part5#5__dos_via_ajax">DoS via AJAX</A></LI> <LI class="L2"> <A href="/part5#5__phishing_via_ajax">Phishing via AJAX</A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__other_vulnerabilities"> Other Vulnerabilities </A> </H2> <UL> <LI class="L2"> <A href="/part5#5__buffer_and_integer_overflow">Buffer Overflow and Integer Overflow</A></LI> <LI class="L2"> <A href="/part5#5__sql_injection"> SQL Injection </A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__after_the_codelab">After the Codelab</A></LI> </UL> </DIV> <DIV class="column2"> <P></P> <P></P> <P> <NOAUTOLINK> </NOAUTOLINK></P> <H2><A name="4__path_traversal"> </A> Path Traversal </H2> <P> Most web applications serve static resources like images and CSS files. Frequently, applications simply serve all the files in a folder. If the application isn't careful, the user can use a path traversal attack to read files from other folders that they shouldn't have access to. For example, in both Windows and Linux, <CODE>..</CODE> represents the parent directory, so if you can inject <CODE>../</CODE> in a path you can "escape" to the parent directory. </P> <P> If an attacker knows the structure of your file system, then they can craft a URL that will traverse out of the installation directory to <CODE>/etc</CODE>. For example, if Picasa was vulnerable to path traversal (it isn't) and the Picasa servers use a Unix-like system, then the following would retrieve the password file: </P> <P></P> <PRE> https://www.picasa.com/../../../../../../../etc/passwd </PRE> <P></P> <P></P> <H3><A name="4__information_disclosure_path_traversal"> </A> Information disclosure via path traversal </H3> <P> <IMG src="/static/cheese_bw.png" style="vertical-align:middle; padding-right: 5px;"> <B>Find a way to read <CODE>secret.txt</CODE> from a running Gruyere server.</B> </P> <P> Amazingly, this attack is not even necessary in many cases: people often install applications and never change the defaults. So the first thing an attacker would try is the default value. </P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'read_secret_txt_hint');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="read_secret_txt_hint" style="display:none"> <P> This isn't a black box attack because you need to know that the <CODE>secret.txt</CODE> file exists, where it's stored, and where Gruyere stores its resource files. You don't need to look at any source code. </P> <P></P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'read_secret_txt_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="read_secret_txt_hint2" style="display:none"> <P> How does the server know which URLs represent resource files? You can use curl or a web proxy to craft request URLs that some browsers may not allow. </P> <P></P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'read_secret_txt_sol');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="read_secret_txt_sol" style="display:none"> <P> <B>To exploit,</B> you can steal <CODE>secret.txt</CODE> via this URL: </P> <P></P> <PRE> https://google-gruyere.appspot.com/382665580745386307547168512335551204731/../secret.txt </PRE> <P> Some browsers, like Firefox and Chrome, optimize out <CODE>../</CODE> in URLs. This doesn't provide any security protection because an attacker will use <CODE>%2f</CODE> to represent <CODE>/</CODE> in the URL; or a tool like curl, a web proxy or a browser that doesn't do that optimization. But if you test your application with one of these browsers to see if you're vulnerable, you might think you were protected when you're not. </P> <P> <B>To fix,</B> we need to prevent access to files outside the resources directory. Validating file paths is a bit tricky as there are various ways to hide path elements like "../" or "~" that allow escaping out of the resources folder. The best protection is to only serve specific resource files. You can either hardcode a list or when your application starts, you can crawl the resource directory and build a list of files. Then only accept requests for those files. You can even do some optimization here like caching small files in memory which will make your application faster. If you are going to try to file path validation, you need to do it on the final path, not on the URL, as there are numerous ways to represent the same characters in URLs. <I>Note: Changing file permissions will NOT work. Gruyere has to be able to read this file.</I> </P> <P></P> </DIV> </DIV> <P></P> <H3><A name="4__data_tampering_path_traversal"> </A> Data tampering via path traversal </H3> <P> <IMG src="/static/cheese_bw.png" style="vertical-align:middle; padding-right: 5px;"> <B>Find a way to replace <CODE>secret.txt</CODE> on a running Gruyere server.</B> </P> <P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'write_secret_txt_hint');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="write_secret_txt_hint" style="display:none"> <P> Again, this isn't a black box attack because you need to know about the directory structure that Gruyere uses, specifically where uploaded files are stored. </P> <P></P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'write_secret_txt_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="write_secret_txt_hint2" style="display:none"> <P> If I log in as user <CODE>brie</CODE> and upload a file, where does the server store it? Can you trick the server into uploading a file to <CODE>../../secret.txt</CODE>? </P> <P></P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'write_secret_txt_sol');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="write_secret_txt_sol" style="display:none"> <P> <B>To exploit,</B> create a new user named <CODE>..</CODE> and upload your new <CODE>secret.txt</CODE>. You could also create a user named <CODE>brie/../..</CODE>. </P> <P> <B>To fix,</B> you should escape dangerous characters in the username (replacing them with safe characters) before using it. It was earlier suggested that we should restrict the characters allowed in a username, but it probably didn't occur to you that <CODE>"."</CODE> was a dangerous character. It's worth noting that there's a vulnerability unique to Windows servers with this implementation. On Windows, filenames are not case sensitive but Gruyere usernames are. So one user can attack another user's files by creating a similar username that differs only in case, e.g., <CODE>BRIE</CODE> instead of <CODE>brie</CODE>. So we need to not just escape unsafe characters but convert the username to a canonical form that is different for different usernames. Or we could avoid all these issues by assigning each user a unique identifier instead. </P> <P> <B>Oops!</B> This doesn't completely solve the problem. Even with the above fix in place, there is another way to perform this attack. Can you find it? <H4 style="cursor: pointer" onclick="toggleBlock(this, 'another_write_secret_hint');"><IMG src="/static/closed.gif"> Hint </H4> <DIV id="another_write_secret_hint" style="display:none"> <P> Are there any limits on the filename when you do an upload? You may need to use a special tool like <CODE>curl</CODE> or a web proxy to perform this attack. </P> <P></P> </DIV> </P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'write_secret_txt_sol2');"><IMG src="/static/closed.gif"> Another Exploit and Fix </H4> <DIV id="write_secret_txt_sol2" style="display:none"> <P> Surprisingly, you can upload a file named <CODE>../secret.txt</CODE>. Gruyere provides no protection against this attack. Most browsers won't let you upload that file but, again, you can do it with curl or other tools. You need the same kind of protection when writing files as you do on read. </P> <P> As a general rule, you should never store user data in the same place as your application files but that alone won't protect against these attacks since if the user can inject <CODE>../</CODE> into the file path, they can traverse all the way to the root of the file system and then back down to the normal install location of your application (or even the Python interpreter itself). </P></DIV></DIV> </DIV> <BR><P></P> <H2><A name="4__denial_of_service"> </A> Denial of Service </H2> <P> A denial of service (DoS) attack is an attempt to make a server unable to service ordinary requests. A common form of DoS attack is sending more requests to a server than it can handle. The server spends all its time servicing the attacker's requests that it has very little time to service legitimate requests. Protecting an application against these kinds of DoS attacks is outside the scope of this codelab. And attacking Gruyere in this way would be interpreted as an attack on App Engine. </P><P> Hackers can also prevent a server from servicing requests by taking advantage of server bugs, such as sending requests that crash a server, make it run out of memory, or otherwise cause it fail serving legitimate requests in some way. In the next few challenges, you'll take advantage of bugs in Gruyere to perform DoS attacks. </P> <H3><A name="4__dos_quit_server"> </A> DoS - Quit the Server </H3> The simplest form of denial of service is shutting down a service. <P> <IMG src="/static/cheese_bw.png" style="vertical-align:middle; padding-right: 5px;"> <B>Find a way to make the server quit.</B> </P> <P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'dos_quit_hint');"><IMG src="/static/closed.gif"> Hint </H4> <DIV id="dos_quit_hint" style="display:none"> <P> How does an administrator make the server quit? The server management page is <CODE><A href="/code/?resources/manage.gtl">manage.gtl</A></CODE>. </P> <P></P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'dos_quit_soln');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="dos_quit_soln" style="display:none"> <P> <B>To exploit,</B> make a request to <CODE>https://google-gruyere.appspot.com/382665580745386307547168512335551204731/quitserver</CODE>. You should need to be logged in as an administrator to do this, but you don't. </P><P> This is another example of a common bug. The server protects against non-administrators accessing certain URLs but the list includes <CODE>/quit</CODE> instead of the actual URL <CODE>/quitserver</CODE>. </P> <P> <B>To fix,</B> add <CODE>/quitserver</CODE> to the URLS only accessible to administrators: </P><PRE> _PROTECTED_URLS = [ "/quitserver", "/reset" ] </PRE> <P> <!--MARK-A--> <B>Oops!</B> This doesn't completely solve the problem. The <CODE>reset</CODE> URL is in the protected list. Can you figure out how to access it? </P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'dos_bonus_hint');"><IMG src="/static/closed.gif"> Hint </H4> <DIV id="dos_bonus_hint" style="display:none"> <P> Look carefully at the code that handles URLs and checks for protected ones. </P></DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'dos_bonus_soln');"><IMG src="/static/closed.gif"> Another Exploit and Fix </H4> <DIV id="dos_bonus_soln" style="display:none"> <P> <B>To exploit,</B> use <CODE>https://google-gruyere.appspot.com/382665580745386307547168512335551204731/RESET</CODE>. The check for protected urls is case sensitive. After doing that check, it capitalizes the string to look up the implementation. This is a classic check/use bug where the condition being checked does not match the actual use. This vulnerability is worse than the previous one because it exposes all the protected urls. </P><P> <B>To fix,</B> put the security check inside the dangerous functions rather than outside them. That ensures that no matter how we get there, the security check can't be skipped. </P></DIV> </DIV> </DIV> <P> <H3><A name="4__dos_overload_server"></A> DoS - Overloading the Server </H3> <P> <IMG src="/static/cheese_b.png" style="vertical-align:middle; padding-right: 5px;"> <B>Find a way to overload the server when it processes a request.</B> </P> <P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'dos_overload_hint1');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="dos_overload_hint1" style="display:none"> <P> You can upload a template that does this. </P></DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'dos_overload_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="dos_overload_hint2" style="display:none"> <P> Every page includes the <CODE><A href="/code/?resources/menubar.gtl">menubar.gtl</A></CODE> template. Can you figure out how to make that template overload the server? </P></DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'dos_overload_soln');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="dos_overload_soln" style="display:none"> <P> <B>To exploit,</B> create a file named <CODE>menubar.gtl</CODE> containing: <PRE> [[include:menubar.gtl]]DoS[[/include:menubar.gtl]] </PRE> and upload it to the <CODE>resources</CODE> directory using a path traversal attack, e.g., creating a user named <CODE>../resources</CODE>. </P> <P> <B>To fix,</B> implement the protections against path traversal and uploading templates discussed earlier. </P> </DIV> <B>NOTE:</B> After performing the previous exploit, you'll need to push the <A href="/part1#1__reset_button">reset button</A>. </P> </DIV> <H3><A name="4__more_dos"> </A> More on Denial of Service </H3> <P> Unlike a well defined vulnerability like XSS or XSRF, denial of service describes a wide class of attacks. This might mean bringing your service down or flooding your inbox so you can't receive legitimate mail. Some things to consider: </P> <P></P> <UL> <LI><P> If you were evil and greedy, how quickly could you take down your application or starve all of its resources? For example, is it possible for a user to upload their hard drive to your application? Entering the attacker's mindset can help identify DoS points in your application. Additionally, think about where the computationally and memory intensive tasks are in your application and put safeguards in place. Do sanity checks on input values. </P> <LI><P>Put monitoring in place so you can detect when you are under attack and enforce per user quotas and rate limiting to ensure that a small subset of users cannot starve the rest. Abusive patterns could include increased memory usage, higher latency, or more requests or connections than usual. </P> </UL> <!--MARK-B--> <BR><P></P> <H2><A name="4__code_execution"> </A> Code Execution </H2> <P> If an attacker can execute arbitrary code remotely on your server, it's usually game over. They may be able to take control over the running program or potentially break out the process to open a new shell on the computer. From here, it's usually not hard to compromise the entire machine the server is running on. </P> <P> Similar to information disclosure and denial of service, there is no recipe or specific defense to prevent remote code execution. The program must perform validation of all user input before handling it and where possible, implement functions with least privilege rights. This topic can't be done justice in just a short paragraph, but know that this is likely the scariest results a security bug can have and trumps any of the above attacks. </P> <P></P> <H3><A name="4__code_execution_challenge"> </A> Code Execution Challenge </H3> <P> <IMG src="/static/cheese_w.png" style="vertical-align:middle; padding-right: 5px;"> <B>Find a code execution exploit.</B> </P> <P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'code_upload_hint');"><IMG src="/static/closed.gif"> Hint </H4> <DIV id="code_upload_hint" style="display:none"> <P> You need to use two previous exploits. </P></DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'code_upload_soln');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="code_upload_soln" style="display:none"> <P> <B>To exploit,</B> make a copy of <CODE><A href="/code/?gtl.py">gtl.py</A></CODE> (or <CODE><A href="/code/?sanitize.py">sanitize.py</A></CODE>) and add some exploit code. Now you can either upload a file named <CODE>../gtl.py</CODE> or create a user named <CODE>..</CODE> and upload <CODE>gtl.py</CODE>. Then, make the server quit by browsing to <CODE>https://google-gruyere.appspot.com/382665580745386307547168512335551204731/quitserver</CODE>. When the server restarts, your code will run. </P> <P> This attack was possible because Gruyere has permission to both read and write files in the Gruyere directory. Applications should run with the minimal privileges possible. </P> <P> Why would you attack <CODE>gtl.py</CODE> or <CODE>sanitize.py</CODE> rather than <CODE>gruyere.py</CODE>? When an attacker has a choice, they would usually choose to attack the infrastructure rather than the application itself. The infrastructure is less likely to be updated and less likely to be noticed. When was the last time you checked that no one had replaced <CODE>python.exe</CODE> with a trojan? </P> <P> <B>To fix,</B> fix the two previous exploits. </P> <P></P> </DIV> </DIV> <P></P> <H3><A name="4__more_code_execution"> </A> More on Remote Code Execution </H3> <P> Even though there is no single or simple defense to remote code execution, here is a short list of some preventative measures: </P> <UL> <LI><P><B>Least Privilege:</B> Always run your application with the <A href="https://www.google.com/search?q=least+privileges" target="_top">least privileges</A> it needs. </P> <LI><P><B>Application Level Checks:</B> Avoid passing user input directly into commands that evaluate arbitrary code, like <CODE>eval()</CODE> or <CODE>system()</CODE>. Instead, use the user input as a switch to choose from a set of developer controlled commands. </P> <LI><P><B>Bounds Checks:</B> Implement proper bounds checks for non-safe languages like C++. Avoid <A href="https://www.google.com/search?q=unsafe+string+functions" target="_top">unsafe string functions</A>. Keep in mind that even safe languages like Python and Java use native libraries. </P> </UL> <!--MARK-C--> <BR><P></P> <FONT SIZE="+2"> <A href="/part5">Continue >></A> </FONT><BR> <BR> <P style="font-size:x-small"> © Google 2017 <A href="https://www.google.com/intl/en/policies/terms/">Terms of Service</A> <BR>The code portions of this codelab are licensed under the Creative Commons Attribution-No Derivative Works 3.0 United States license <<A href="https://creativecommons.org/licenses/by-nd/3.0/us/">https://creativecommons.org/licenses/by-nd/3.0/us</A>>. Brief excerpts of the code may be used for educational or instructional purposes provided this notice is kept intact. Except as otherwise noted the remainder of this codelab is licensed under the Creative Commons Attribution 3.0 United States license <<A href="https://creativecommons.org/licenses/by/3.0/us/">https://creativecommons.org/licenses/by/3.0/us</A>>. </P> </BODY></HTML> |
| URL | https://google-gruyere.appspot.com/part5 |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 365 bytes. |
GET https://google-gruyere.appspot.com/part5 HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: https://google-gruyere.appspot.com/ Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 301 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: caef2034c3d8cd73b1c67904e7d53a00 Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 25303 Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 |
| Response Body - size: 25,303 bytes. |
<HTML><HEAD><META http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<TITLE>Web Application Exploits and Defenses</TITLE> <LINK type="text/css" rel="stylesheet" href="../static/codelab.css"> <!--MARK-Z--> <SCRIPT> function toggleBlock(heading, whichID) { var image = heading.childNodes[0]; var block = document.getElementById(whichID); if (block) { if (getDisplay(block) == 'block') { block.style.display = 'none'; image.src = 'static/closed.gif'; } else { // "none" or "" block.style.display = 'block'; image.src = 'static/open.gif'; } } } function getDisplay(block) { var value = block.style.display; if (!value) { if (document.defaultView) { var computedStyle = document.defaultView.getComputedStyle(block, ""); value = computedStyle.getPropertyValue('display'); } else if (block.currentStyle) { value = block.currentStyle.display; } } return value; } </SCRIPT> </HEAD> <BODY bgcolor="#ffffff"> <DIV class="banner"></DIV> <H1><FONT size="+2"><IMG src="/static/gruyere-78.png" style="vertical-align:middle"> Web Application Exploits and Defenses (Part 5)</FONT> </H1> <P> A Codelab by Bruce Leban, Mugdha Bendre, and Parisa Tabriz </P> <DIV class="printable"> <DIV class="column1"> Table of Contents <UL> <LI class="L1" style="padding-top:0pt"> <A href="/#0__hackers">Beat the hackers</A></LI> <LI class="L1" style="padding-top:0pt"> <A href="/#0__gruyere">Gruyere</A></LI> <LI class="L1"> <A href="/part1#1__setup">Set-up</A> <UL> <LI class="L2"> <A href="/part1#1__reset_button">Reset Button</A></LI> <LI class="L2"> <A href="/part1#1__about_the_code">About the Code</A></LI> <LI class="L2"> <A href="/part1#1__features_and_technologies">Features and Technologies</A></LI> </UL></LI> <LI class="L1"> <A href="/part1#1__using_gruyere">Using Gruyere</A></LI> <LI class="L1"> <A href="/part2#2__cross_site_scripting">Cross-Site Scripting (XSS)</A> <UL> <LI class="L2"> <A href="/part2#2__xss_challenge">XSS Challenges</A></LI> <LI class="L2"> <A href="/part2#2__file_upload_xss">File Upload XSS</A></LI> <LI class="L2"> <A href="/part2#2__reflected_xss">Reflected XSS</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss">Stored XSS</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss_via_html_attribute">Stored XSS via HTML Attribute</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss_via_ajax">Stored XSS via AJAX</A></LI> <LI class="L2"> <A href="/part2#2__reflected_xss_via_ajax">Reflected XSS via AJAX</A></LI> <LI class="L2"> <A href="/part2#2__more_about_xss">More about XSS</A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__client_state_manipulation">Client-State Manipulation</A> <UL> <LI class="L2"> <A href="/part3#3__elevation_of_privilege"> Elevation of Privilege </A></LI> <LI class="L2"> <A href="/part3#3__cookie_manipulation"> Cookie Manipulation </A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__cross_site_request_forgery">Cross-Site Request Forgery (XSRF)</A> <UL> <LI class="L2"> <A href="/part3#3__xsrf_challenge">XSRF Challenge</A></LI> <LI class="L2"> <A href="/part3#3__more_about_preventing_xsrf">More about preventing XSRF</A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__cross_site_script_inclusion">Cross Site Script Inclusion (XSSI)</A> <UL> <LI class="L2"> <A href="/part3#3__xssi_challenge">XSSI Challenge</A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__path_traversal">Path Traversal</A> <UL> <LI class="L2"> <A href="/part4#4__information_disclosure_path_traversal">Information disclosure via path traversal </A></LI> <LI class="L2"> <A href="/part4#4__data_tampering_path_traversal">Data tampering via path traversal </A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__denial_of_service">Denial of Service</A> <UL> <LI class="L2"> <A href="/part4#4__dos_quit_server">DoS - Quit the Server</A></LI> <LI class="L2"> <A href="/part4#4__dos_overload_server">DoS - Overloading the Server</A></LI> <LI class="L2"> <A href="/part4#4__more_dos">More on Denial of Service</A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__code_execution">Code Execution</A> <UL> <LI class="L2"> <A href="/part4#4__code_execution_challenge">Code Execution Challenge</A></LI> <LI class="L2"> <A href="/part4#4__more_code_execution">More on Remote Code Execution</A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__configuration_vulnerabilities">Configuration Vulnerabilities</A> <UL> <LI class="L2"> <A href="/part5#5__information_disclosure_config_1">Information disclosure #1</A></LI> <LI class="L2"> <A href="/part5#5__information_disclosure_config_2">Information disclosure #2</A></LI> <LI class="L2"> <A href="/part5#5__information_disclosure_bug_3">Information disclosure #3 </A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__ajax_vulnerabilities">AJAX vulnerabilities</A> <UL> <LI class="L2"> <A href="/part5#5__dos_via_ajax">DoS via AJAX</A></LI> <LI class="L2"> <A href="/part5#5__phishing_via_ajax">Phishing via AJAX</A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__other_vulnerabilities"> Other Vulnerabilities </A> </H2> <UL> <LI class="L2"> <A href="/part5#5__buffer_and_integer_overflow">Buffer Overflow and Integer Overflow</A></LI> <LI class="L2"> <A href="/part5#5__sql_injection"> SQL Injection </A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__after_the_codelab">After the Codelab</A></LI> </UL> </DIV> <DIV class="column2"> <P></P> <P></P> <P> <NOAUTOLINK> </NOAUTOLINK></P> <H2><A name="5__configuration_vulnerabilities"> </A> Configuration Vulnerabilities </H2> <P> Applications are often installed with default settings that attackers can use to attack them. This is particularly an issue with third party software where an attacker has easy access to a copy of the same application or framework you are running. Hackers know the default account names and passwords. For example, looking at the contents of <CODE><A href="/code/?data.py">data.py</A></CODE> you know that there's a default administrator account named 'admin' with the password 'secret'. </P> <P> Configuration vulnerabilities also include features that increase attack surface. A common example is a feature that is on by default but you are not using, so you didn't configure it and the default configuration is vulnerable. It also includes debug features like status pages or dumping stack traces on failures. </P> <P></P> <H3><A name="5__information_disclosure_config_1"> </A> Information disclosure #1 </H3> <P> <IMG src="/static/cheese_w.png" style="vertical-align:middle; padding-right: 5px;"> <B>Read the contents of the database off of a running server by exploiting a configuration vulnerability.</B> </P> <P>You should look through the Gruyere code looking for default configurations or debug features that don't belong there.</P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'disclosure_1_hint');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="disclosure_1_hint" style="display:none"> <P> Look at all the files installed with Gruyere. Are there any files that shouldn't be there? </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'disclosure_1_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="disclosure_1_hint2" style="display:none"> <P> Look for a <CODE>.gtl</CODE> file that isn't referenced anywhere. </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'disclosure_1_sol');"><IMG src="/static/closed.gif"> Exploit and Fixes </H4> <DIV id="disclosure_1_sol" style="display:none"> <P> <B>To exploit,</B> you can use the debug dump page <CODE><A href="/code/?resoources/dump.gtl">dump.gtl</A></CODE> to display the contents of the database via the following URL: </P><PRE> https://google-gruyere.appspot.com/382665580745386307547168512335551204731/dump.gtl </PRE> <P> <B>To fix,</B> always make sure debug features are not installed. In this case, delete <CODE>dump.gtl</CODE>. This is an example of the kind of debug feature that might be left in an application by mistake. If a debug feature like this is necessary, then it needs to be carefully locked down: only admin users should have access and only requests from debug IP addresses should be accepted. </P><P> This exploit exposes the users' passwords. Passwords should never be stored in cleartext. Instead, you should use <A href="https://www.google.com/search?q=password+hashing">password hashing</A>. The idea is that to authenticate a user, you don't need to know their password, only be convinced that the user knows it. When the user sets their password, you store only a cryptographic hash of the password and a salt value. When the user re-enters their password later, you recompute the hash and if it matches you conclude the password is correct. If an attacker obtains the hash value, it's very difficult for them to reverse that to find the original password. (Which is a good thing, since despite <A href="https://www.google.com/search?q=choosing+a+good+password">lots of advice</A> to the contrary, users frequently use the same weak passwords for multiple sites.) </P></DIV> </DIV> <P></P> <H3><A name="5__information_disclosure_config_2"> </A> Information disclosure #2 </H3> <P> <IMG src="/static/cheese_w.png" style="vertical-align:middle; padding-right: 5px;"> <B>Even after implementing the fix described above, an attacker can undo it and execute the attack! How can that be?</B> </P> <P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'disclosure_2_hint');"><IMG src="/static/closed.gif"> Hint </H4> <DIV id="disclosure_2_hint" style="display:none"> <P> You can upload a file of any type. </P> <P></P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'disclosure_2_sol');"><IMG src="/static/closed.gif"> Exploit and Fixes </H4> <DIV id="disclosure_2_sol" style="display:none"> <P> <B>To exploit,</B> Gruyere allows the user to upload files of any type, including <CODE>.gtl</CODE> files. So the attacker can simply upload their own copy of <CODE><A href="/code/?resources/dump.gtl">dump.gtl</A></CODE> or a similar file and than access it. In fact, as noted earlier, hosting arbitrary content on the server is a major security risk whether it's HTML, JavaScript, Flash or something else. Allowing a file with an unknown file type may lead to a security hole in the future. </P> <P> <B>To fix,</B> we should do several things: </P><OL> <LI> Only files that are part of Gruyere should be treated as templates. </LI> <LI> Don't store user uploaded files in the same place as application files. </LI> <LI> Consider limiting the types of files that can be uploaded (via a whitelist). </LI> </OL> </DIV> </DIV> <H3><A name="5__information_disclosure_bug_3"> </A> Information disclosure #3 </H3> <P> <IMG src="/static/cheese_w.png" style="vertical-align:middle; padding-right: 5px;"> <B>Even after implementing the fixes described above, a similar attack is still possible through a different attack vector. Can you find it?</B> </P> <P> This attack isn't a a configuration vulnerability, just bad code. </P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'disclosure_3_hint1');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="disclosure_3_hint1" style="display:none"> <P>You can insert something in your private snippet which will display the contents of the database.</P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'disclosure_3_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="disclosure_3_hint2" style="display:none"> <P>This attack is closely related to the previous ones. There is a bug in the code that expands templates that you can exploit.</P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'disclosure_3_sol');"><IMG src="/static/closed.gif"> Exploit and Fixes </H4> <DIV id="disclosure_3_sol" style="display:none"> <P> There is a defect in Gruyere's template expansion code that reparses expanded variables. Specifically, when expanding a block it expands variables in the block. Then it parses the block as a template and expands variables again, </P> <B>To exploit,</B> add this to your private snippet: <PRE> {{_db:pprint}} </PRE> </P> <P> <B>To fix,</B> modify the template code so it never reparses inserted variable values. The defect in the code is due to the fact that <CODE>ExpandTemplate</CODE> calls <CODE>_ExpandBlocks</CODE> followed by <CODE>_ExpandVariables</CODE>, but <CODE>_ExpandBlocks</CODE> calls <CODE>ExpandTemplate</CODE> on nested blocks. So if a variable is expanded inside a nested block and contains something that looks like a variable template, it will get expanded a second time. That sounds complicated because it is complicated. Parsing blocks and variables separately is a fundamental flaw in the design of the expander, so the fix is non-trivial. </P> <P> This exploit is possible because the template language allows arbitrary database access. It would be safer if the templates were only allowed to access data specifically provided to them. For example, a template could have an associated database query and only the data matched by that query would be passed to the template. This would limit the scope of a bug like this to data that the user was already allowed to access. </P> </DIV> </DIV> <BR><BR> </P><H2><A name="5__ajax_vulnerabilities"> </A> AJAX vulnerabilities </H2> Bad AJAX code allows attackers to modify parts of your application in ways that you might not expect. In traditional client development, there is a clear separation between the application and the data it displays. That's not true in web applications as the next two attacks will make clear. <P></P> <H3><A name="5__dos_via_ajax"> </A> DoS via AJAX </H3> <P> <IMG src="/static/cheese_b.png" style="vertical-align:middle; padding-right: 5px;"> <B>Find an attack that prevents users from seeing their private snippets on the home page.</B> (The attack should be triggered after clicking the refresh link and without using XSS.) </P> <P></P> <DIV style="margin-left:18pt"> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'dom_hint');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="dom_hint" style="display:none"> <P> Can you figure out how to change the value of the private snippet in the AJAX response? </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'dom_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="dom_hint2" style="display:none"> <P> What happens if a JSON object has a duplicate key value? </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'dom_sol');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="dom_sol" style="display:none"> <P> <B>To exploit,</B> create a user named <CODE>private_snippet</CODE> and create at least one snippet. The JSON response will then be <CODE>{'private_snippet' : <user's private snippet>, ..., 'private_snippet' : <attacker's snippet>}</CODE> and the attacker's snippet replaces the user's. </P> <P> <B>To fix,</B> the AJAX code needs to make sure that the data only goes where it's supposed to go. The flaw here is that the JSON structure is not robust. A better structure would be <CODE>[<private_snippet>, {<user> : <snippet>,...}]</CODE>. </P> <P></P> </DIV> </DIV> <P></P> <H3><A name="5__phishing_via_ajax"> </A> Phishing via AJAX </H3> <P> While the previous attack may seem like a minor inconvenience, careless DOM manipulation can lead to much more serious problems. </P> <P> <IMG src="/static/cheese_b.png" style="vertical-align:middle; padding-right: 5px;"> <B>Find a way to change the sign in link in the upper right corner to point to <CODE>https://evil.example.com</CODE>.</B> </P> <P> (The attack should be triggered after clicking the refresh link and without using XSS or a script.) </P> <P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'phishing_hint');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="phishing_hint" style="display:none"> <P> Look at what the script does to replace the snippets on the page. Can you get it to replace the sign in link? </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'phishing_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="phishing_hint2" style="display:none"> <P> Look at the AJAX code to see how it replaces each snippet, and then look at the structure of the home page and see if you can see what else you might be able to replace. (You can't just replace the sign in link. You'll have to replace a bit more.) </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'phishing_sol');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="phishing_sol" style="display:none"> <P> <B>To exploit,</B> create a user named <CODE>menu-right</CODE> and publish a snippet that looks exactly like the right side of the menu bar. </P> <P></P> <PRE> <a href='https://evil.example.com/login'>Sign in</a> | <a href='https://evil.example.com/newaccount.gtl'>Sign up</a> </PRE> <P> If the user is already logged in, the menu bar will look wrong. But that's ok, since there's a good chance the user will just think they somehow accidentally got logged out of the web site and log in again. </P> <P> <B>To fix,</B> the process of modifying the DOM needs to be made more robust. When user values are used in as DOM element identifiers, you should ensure that there can't be a conflict as there is here, for example, applying a prefix to user values like <CODE>id="user_<USER>"</USER></CODE>. Even better, use your own identifiers rather than user values. </P> <P> This spoofing attack is easily detected when the user clicks Sign in and ends up at <CODE>evil.example.com</CODE>. A clever attacker could do something harder to detect, like replacing the Sign in link with a script that renders the sign in form on the current page with the form submission going to their server. </P> <P></P> </DIV> </DIV> <BR><P></P> <P></P> <H2><A name="5__other_vulnerabilities"> </A> Other Vulnerabilities </H2> <H3><A name="5__buffer_and_integer_overflow"> </A> Buffer Overflow and Integer Overflow </H3> <DIV><P>A <A href="https://www.google.com/search?q=buffer+overflow" target="_top">buffer overflow</A> vulnerability exists when an application does not properly guard its buffers and allow user data to write past the end of a buffer. This excess data can modify other variables, including pointers and function return addresses, leading to arbitrary code execution. Historically, buffer overflow vulnerabilities have been responsible for some of the most widespread internet attacks including <a href="https://www.google.com/search?q=sql+slammer">SQL Slammer<a>, <a href="https://www.google.com/search?q=blaster+worm">Blaster</a> and <a href="https://www.google.com/search?q=code+red+worm">Code Red</a> computer worms. The PS2, Xbox and Wii have all been hacked using buffer overflow exploits. </P> <P>While not as well known, <A href="https://www.google.com/search?q=integer+overflow+vulnerability" target="_top">integer overflow</A> vulnerabilities can be just as dangerous. Any time an integer computation silently returns an incorrect result, the application will operate incorrectly. In the best case, the application fails. In the worst case, there is a security bug. For example, if an application checks that <CODE>length + 1 < limit</CODE> then this will succeed if <CODE>length</CODE> is the largest positive integer value, which can then expose a buffer overflow vulnerability. </P> <P>This codelab doesn't cover overflow vulnerabilities because Gruyere is written in Python, and therefore not vulnerable to typical buffer and integer overflow problems. Python won't allow you to read or write outside the bounds of an array and integers can't overflow. While C and C++ programs are most commonly known to expose these vulnerabilities, other languages are not immune. For example, while Java was designed to prevent buffer overflows, it silently ignores integer overflow. </P> <P> Like all applications, Gruyere is vulnerable to platform vulnerabilities. That is, if there are security bugs in the platforms that Gruyere is built on top of, then those bugs would also apply to Gruyere. Gruyere's platform includes: the Python runtime system and libraries, AppEngine, the operating system that Gruyere runs on and the client side software (including the web browser) that users use to run Gruyere. While platform vulnerabilities are important, they are outside the scope of this codelab as you generally can't fix platform vulnerabilities by making changes to your application. Fixing platform vulnerabilities yourself is also not practical for many people, but you can mitigate your risks by making sure that you are diligent in applying security updates as they are released by platform vendors. </P> </DIV> <!--MARK-D--> <H3><A name="5__sql_injection"> </A> SQL Injection </H3> <DIV><P>Just as XSS vulnerabilities allow attackers to inject script into web pages, <a href="https://www.google.com/search?q=sql+injection">SQL injection</a> vulnerabilities allow attackers to inject arbitrary scripts into SQL queries. When a SQL query is executed it can either read or write data, so an attacker can use SQL injection to read your entire database as well as overwrite it, as described in the classic <a href="https://xkcd.com/327/">Bobby Tables</a> XKCD comic. If you use SQL, the most important advice is to avoid building queries by string concatenation: use API calls instead. This codelab doesn't cover SQL injection because Gruyere doesn't use SQL. </P> </DIV> <BR><BR> <H2><A name="5__after_the_codelab"> </A> After the Codelab </H2> <P> We hope that you found this codelab instructive. If you want more practice, there are many more security bugs in Gruyere than the ones described above. You should attack your own application using what you've learned and write unit tests to verify that these bugs are not present and won't get introduced in the future. You should also consider using <A href="https://www.google.com/search?q=fuzz+testing" target="_top">fuzz testing</A> tools. For more information about security at Google, please visit our <A href="https://security.googleblog.com/" target="_top">blog</A> or our <A href="https://www.google.com/about/appsecurity/" target="_top">corporate security page</A>. </P> <P> If you'd like to share this codelab with others, please consider tweeting or buzzing about it or posting one of these badges on your blog or personal page: <!--MARK-E--> </P> <div> <table style="border-collapse:collapse;border:solid 1 black" cellpadding="5"> <tr><td style="border:solid 1 black;text-align:center;vertical-align:middle"> <a href="https://google-gruyere.appspot.com/"> <img src="../static/gruyere-badge.png" style="padding:4pt" border="0" alt="Learn how to make web apps more secure. Do the Gruyere codelab."></a> </td><td style="border:solid 1 black;vertical-align:middle"> <pre style="margin:0"> <a href="https://google-gruyere.appspot.com/"> <img src="//google-gruyere.appspot.com/static/gruyere-badge.png" style="padding:4pt" border="0" alt="Learn how to make web apps more secure. Do the Gruyere codelab."></a> </pre> <div align="center" style="margin:0">(Line breaks above should be copied verbatim.)</div> </td></tr><tr><td style="border:solid 1 black;text-align:center;vertical-align:middle"> <a href="https://google-gruyere.appspot.com/"> <img src="../static/gruyere-40.png" style="padding:4pt" border="0" title="Learn how to make web apps more secure. Do the Gruyere codelab." alt="Learn how to make web apps more secure. Do the Gruyere codelab."></a> </td><td style="border:solid 1 black;vertical-align:middle"> <pre style="margin:0"> <a href="https://google-gruyere.appspot.com/"> <img src="//google-gruyere.appspot.com/static/gruyere-40.png" style="padding:4pt" border="0" title="Learn how to make web apps more secure. Do the Gruyere codelab." alt="Learn how to make web apps more secure. Do the Gruyere codelab."></a> </pre> <div align="center" style="margin:0">(Line breaks above should be copied verbatim.)</div> </td></tr></table></div> <!--MARK-F--> <!--NEXTLINK--> <BR> <P style="font-size:x-small"> © Google 2017 <A href="https://www.google.com/intl/en/policies/terms/">Terms of Service</A> <BR>The code portions of this codelab are licensed under the Creative Commons Attribution-No Derivative Works 3.0 United States license <<A href="https://creativecommons.org/licenses/by-nd/3.0/us/">https://creativecommons.org/licenses/by-nd/3.0/us</A>>. Brief excerpts of the code may be used for educational or instructional purposes provided this notice is kept intact. Except as otherwise noted the remainder of this codelab is licensed under the Creative Commons Attribution 3.0 United States license <<A href="https://creativecommons.org/licenses/by/3.0/us/">https://creativecommons.org/licenses/by/3.0/us</A>>. </P> </BODY></HTML> |
| URL | https://google-gruyere.appspot.com/start |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 369 bytes. |
GET https://google-gruyere.appspot.com/start HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: https://google-gruyere.appspot.com/123/ Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 281 bytes. |
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8 Cache-Control: no-cache X-Cloud-Trace-Context: f47ca7b10fb640127b87f1e5dad0bcfc Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 756 Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 |
| Response Body - size: 756 bytes. |
<HTML>
<STYLE> body, th, td, form { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; } h1 { color: #dd0000; } </STYLE> <TITLE>Start Gruyere</TITLE> <BODY> <H1>Start Gruyere</H1> Your Gruyere instance id is 382665580745386307547168512335551204731. <br><br><span style="color:red"><b>WARNING: Gruyere is not secure.<br> Do not upload any personal or private data.</b></span> <br><br>By using Gruyere you agree to the <A href="https://www.google.com/intl/en/policies/terms/">terms of service</A>. <H2><A HREF="/382665580745386307547168512335551204731">Resume</A></H2> <H2><A HREF="/resetbutton/382665580745386307547168512335551204731">Reset</A></H2> </BODY></HTML> |
| URL | https://google-gruyere.appspot.com/static/codeindex.html |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 386 bytes. |
GET https://google-gruyere.appspot.com/static/codeindex.html HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: https://google-gruyere.appspot.com/code/ Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 334 bytes. |
HTTP/1.1 200 OK
Date: Tue, 06 Feb 2024 17:42:24 GMT Expires: Tue, 06 Feb 2024 17:52:24 GMT Cache-Control: public, max-age=600 ETag: "3m8CBg" X-Cloud-Trace-Context: ff18a02873c19d522ffa7bb39a8c011b Content-Type: text/html Server: Google Frontend Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 content-length: 2188 |
| Response Body - size: 2,188 bytes. |
<HTML>
<HEAD> <TITLE>Gruyere Code</TITLE> <STYLE> a {color:black; text-decoration:none} a:visited{color:#6600cc;} a:focus, a:hover {color:#003399; text-decoration: underline;} a:active{color:#cc33cc;} big {color: #660066; } </STYLE> </HEAD> <BODY> <TT> <B><BIG><BIG>Gruyere<BR>Code</BIG></BIG></B><BR><BR> <A href="/code/gruyere.py" target="codepage">gruyere.py</A><BR> <A href="/code/data.py" target="codepage">data.py</A><BR> <A href="/code/sanitize.py" target="codepage">sanitize.py</A><BR> <A href="/code/gtl.py" target="codepage">gtl.py</A><BR> <A href="/code/secret.txt" target="codepage">secret.txt</A><BR> <BR> resources/<BR> <A href="/code/resources/base.css" target="codepage">base.css</A><BR> <A href="/code/resources/dump.gtl" target="codepage">dump.gtl</A><BR> <A href="/code/resources/editprofile.gtl" target="codepage">editprofile.gtl</A><BR> <A href="/code/resources/error.gtl" target="codepage">error.gtl</A><BR> <A href="/code/resources/feed.gtl" target="codepage">feed.gtl</A><BR> <A href="/code/resources/home.gtl" target="codepage">home.gtl</A><BR> <A href="/code/resources/lib.js" target="codepage">lib.js</A><BR> <A href="/code/resources/login.gtl" target="codepage">login.gtl</A><BR> <A href="/code/resources/manage.gtl" target="codepage">manage.gtl</A><BR> <A href="/code/resources/menubar.gtl" target="codepage">menubar.gtl</A><BR> <A href="/code/resources/newaccount.gtl" target="codepage">newaccount.gtl</A><BR> <A href="/code/resources/newsnippet.gtl" target="codepage">newsnippet.gtl</A><BR> <A href="/code/resources/showprofile.gtl" target="codepage">showprofile.gtl</A><BR> <A href="/code/resources/snippets.gtl" target="codepage">snippets.gtl</A><BR> <A href="/code/resources/upload.gtl" target="codepage">upload.gtl</A><BR> <A href="/code/resources/upload2.gtl" target="codepage">upload2.gtl</A><BR> <BR> <I><A href="/gruyere-code.zip" target="_top">Download zip file</A></I><BR> <BR> <A href="/" target="_top"><< back to codelab</A> </TT> </BODY> </HTML> |
| URL | https://google-gruyere.appspot.com/static/codeindex/html |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 386 bytes. |
GET https://google-gruyere.appspot.com/static/codeindex/html HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: https://google-gruyere.appspot.com/code/ Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 263 bytes. |
HTTP/1.1 404 Not Found
X-Cloud-Trace-Context: 46ac84ee72f446ec80d8a8181836d001 Date: Tue, 06 Feb 2024 17:42:24 GMT Content-Type: text/html; charset=UTF-8 Server: Google Frontend Content-Length: 298 Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 |
| Response Body - size: 298 bytes. |
<html><head>
<meta http-equiv="content-type" content="text/html;charset=utf-8"> <title>404 Not Found</title> </head> <body text=#000000 bgcolor=#ffffff> <h1>Error: Not Found</h1> <h2>The requested URL <code>/static/codeindex/html</code> was not found on this server.</h2> <h2></h2> </body></html> |
| Instances | 75 |
| Solution |
Ensure that your web server, application server, load balancer, etc. is configured to set the Content-Security-Policy header.
|
| Reference |
https://developer.mozilla.org/en-US/docs/Web/Security/CSP/Introducing_Content_Security_Policy
https://cheatsheetseries.owasp.org/cheatsheets/Content_Security_Policy_Cheat_Sheet.html https://www.w3.org/TR/CSP/ https://w3c.github.io/webappsec-csp/ https://web.dev/articles/csp https://caniuse.com/#feat=contentsecuritypolicy https://content-security-policy.com/ |
| Tags |
OWASP_2021_A05
OWASP_2017_A06 |
| CWE Id | 693 |
| WASC Id | 15 |
| Plugin Id | 10038 |
|
Medium |
Missing Anti-clickjacking Header |
|---|---|
| Description |
The response does not include either Content-Security-Policy with 'frame-ancestors' directive or X-Frame-Options to protect against 'ClickJacking' attacks.
|
| URL | http://google-gruyere.appspot.com/ |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 359 bytes. |
GET http://google-gruyere.appspot.com/ HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/2 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 244 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: d7fb23bba6f0427f282d46dfaf994463 Date: Tue, 06 Feb 2024 17:42:18 GMT Server: Google Frontend Content-Length: 11506 |
| Response Body - size: 11,506 bytes. |
<HTML><HEAD><META http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<TITLE>Web Application Exploits and Defenses</TITLE> <LINK type="text/css" rel="stylesheet" href="../static/codelab.css"> <!--MARK-Z--> <SCRIPT> function toggleBlock(heading, whichID) { var image = heading.childNodes[0]; var block = document.getElementById(whichID); if (block) { if (getDisplay(block) == 'block') { block.style.display = 'none'; image.src = 'static/closed.gif'; } else { // "none" or "" block.style.display = 'block'; image.src = 'static/open.gif'; } } } function getDisplay(block) { var value = block.style.display; if (!value) { if (document.defaultView) { var computedStyle = document.defaultView.getComputedStyle(block, ""); value = computedStyle.getPropertyValue('display'); } else if (block.currentStyle) { value = block.currentStyle.display; } } return value; } </SCRIPT> </HEAD> <BODY bgcolor="#ffffff"> <DIV class="banner"></DIV> <H1><FONT size="+2"><IMG src="/static/gruyere-78.png" style="vertical-align:middle"> Web Application Exploits and Defenses <!--PART#--></FONT> </H1> <P> A Codelab by Bruce Leban, Mugdha Bendre, and Parisa Tabriz </P> <DIV class="printable"> <DIV class="column1"> Table of Contents <UL> <LI class="L1" style="padding-top:0pt"> <A href="/#0__hackers">Beat the hackers</A></LI> <LI class="L1" style="padding-top:0pt"> <A href="/#0__gruyere">Gruyere</A></LI> <LI class="L1"> <A href="/part1#1__setup">Set-up</A> <UL> <LI class="L2"> <A href="/part1#1__reset_button">Reset Button</A></LI> <LI class="L2"> <A href="/part1#1__about_the_code">About the Code</A></LI> <LI class="L2"> <A href="/part1#1__features_and_technologies">Features and Technologies</A></LI> </UL></LI> <LI class="L1"> <A href="/part1#1__using_gruyere">Using Gruyere</A></LI> <LI class="L1"> <A href="/part2#2__cross_site_scripting">Cross-Site Scripting (XSS)</A> <UL> <LI class="L2"> <A href="/part2#2__xss_challenge">XSS Challenges</A></LI> <LI class="L2"> <A href="/part2#2__file_upload_xss">File Upload XSS</A></LI> <LI class="L2"> <A href="/part2#2__reflected_xss">Reflected XSS</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss">Stored XSS</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss_via_html_attribute">Stored XSS via HTML Attribute</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss_via_ajax">Stored XSS via AJAX</A></LI> <LI class="L2"> <A href="/part2#2__reflected_xss_via_ajax">Reflected XSS via AJAX</A></LI> <LI class="L2"> <A href="/part2#2__more_about_xss">More about XSS</A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__client_state_manipulation">Client-State Manipulation</A> <UL> <LI class="L2"> <A href="/part3#3__elevation_of_privilege"> Elevation of Privilege </A></LI> <LI class="L2"> <A href="/part3#3__cookie_manipulation"> Cookie Manipulation </A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__cross_site_request_forgery">Cross-Site Request Forgery (XSRF)</A> <UL> <LI class="L2"> <A href="/part3#3__xsrf_challenge">XSRF Challenge</A></LI> <LI class="L2"> <A href="/part3#3__more_about_preventing_xsrf">More about preventing XSRF</A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__cross_site_script_inclusion">Cross Site Script Inclusion (XSSI)</A> <UL> <LI class="L2"> <A href="/part3#3__xssi_challenge">XSSI Challenge</A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__path_traversal">Path Traversal</A> <UL> <LI class="L2"> <A href="/part4#4__information_disclosure_path_traversal">Information disclosure via path traversal </A></LI> <LI class="L2"> <A href="/part4#4__data_tampering_path_traversal">Data tampering via path traversal </A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__denial_of_service">Denial of Service</A> <UL> <LI class="L2"> <A href="/part4#4__dos_quit_server">DoS - Quit the Server</A></LI> <LI class="L2"> <A href="/part4#4__dos_overload_server">DoS - Overloading the Server</A></LI> <LI class="L2"> <A href="/part4#4__more_dos">More on Denial of Service</A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__code_execution">Code Execution</A> <UL> <LI class="L2"> <A href="/part4#4__code_execution_challenge">Code Execution Challenge</A></LI> <LI class="L2"> <A href="/part4#4__more_code_execution">More on Remote Code Execution</A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__configuration_vulnerabilities">Configuration Vulnerabilities</A> <UL> <LI class="L2"> <A href="/part5#5__information_disclosure_config_1">Information disclosure #1</A></LI> <LI class="L2"> <A href="/part5#5__information_disclosure_config_2">Information disclosure #2</A></LI> <LI class="L2"> <A href="/part5#5__information_disclosure_bug_3">Information disclosure #3 </A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__ajax_vulnerabilities">AJAX vulnerabilities</A> <UL> <LI class="L2"> <A href="/part5#5__dos_via_ajax">DoS via AJAX</A></LI> <LI class="L2"> <A href="/part5#5__phishing_via_ajax">Phishing via AJAX</A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__other_vulnerabilities"> Other Vulnerabilities </A> </H2> <UL> <LI class="L2"> <A href="/part5#5__buffer_and_integer_overflow">Buffer Overflow and Integer Overflow</A></LI> <LI class="L2"> <A href="/part5#5__sql_injection"> SQL Injection </A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__after_the_codelab">After the Codelab</A></LI> </UL> </DIV> <DIV class="column2"> <P></P> <P></P> <P> <NOAUTOLINK> </NOAUTOLINK></P> <STYLE>.column1 {display:none}</STYLE> <BR><P> <H2><A name="0__hackers"></A>Want to beat the hackers at their own game?</H2> <UL> <LI>Learn how hackers find security vulnerabilities! <LI>Learn how hackers exploit web applications! <LI>Learn how to stop them! </UL> </P> <!--MARK-0--> <P> This codelab shows how web application vulnerabilities can be exploited and how to defend against these attacks. The best way to learn things is by doing, so you'll get a chance to do some real penetration testing, actually exploiting a real application. Specifically, you'll learn the following: </P> <P></P> <UL> <LI> How an application can be attacked using common web security vulnerabilities, like cross-site scripting vulnerabilities (XSS) and cross-site request forgery (XSRF). </LI> <LI> How to find, fix, and avoid these common vulnerabilities and other bugs that have a security impact, such as denial-of-service, information disclosure, or remote code execution. </LI> </UL> <P> To get the most out of this lab, you should have some familiarity with how a web application works (e.g., general knowledge of HTML, templates, cookies, AJAX, etc.). </P> <!--MARK-1--> <BR> <BR> <H2><A name="1__gruyere"> </A> Gruyere </H2> <P> <A href="/static/gruyere.png"> <IMG src="/static/gruyere.png" height="285" border="0" style="float:left; vertical-align:middle; margin-right: 10; margin-bottom: 10"> </A> This codelab is built around <B>Gruyere</B> /ɡruːˈjɛər/ <!--groo-yair--> - a small, cheesy web application that allows its users to publish snippets of text and store assorted files. "Unfortunately," Gruyere has multiple security bugs ranging from cross-site scripting and cross-site request forgery, to information disclosure, denial of service, and remote code execution. The goal of this codelab is to guide you through discovering some of these bugs and learning ways to fix them both in Gruyere and in general. </P> <P> The codelab is organized by types of vulnerabilities. In each section, you'll find a brief description of a vulnerability and a task to find an instance of that vulnerability in Gruyere. Your job is to play the role of a malicious hacker and find and exploit the security bugs. In this codelab, you'll use both black-box hacking and white-box hacking. In <B>black box hacking,</B> you try to find security bugs by experimenting with the application and manipulating input fields and URL parameters, trying to cause application errors, and looking at the HTTP requests and responses to guess server behavior. You do not have access to the source code, although understanding how to view source and being able to view http headers (as you can in Chrome or LiveHTTPHeaders for Firefox) is valuable. Using a web proxy like <A href="https://portswigger.net/burp/" target="_top">Burp</A> or <A href="https://www.owasp.org/index.php/OWASP_Zed_Attack_Proxy_Project" target="_top">ZAP</A> may be helpful in creating or modifying requests. In <B>white-box hacking,</B> you have access to the source code and can use automated or manual analysis to identify bugs. You can treat Gruyere as if it's open source: you can read through the source code to try to find bugs. Gruyere is written in Python, so some familiarity with Python can be helpful. However, the security vulnerabilities covered are not Python-specific and you can do most of the lab without even looking at the code. You can run a local instance of Gruyere to assist in your hacking: for example, you can create an administrator account on your local instance to learn how administrative features work and then apply that knowledge to the instance you want to hack. Security researchers use both hacking techniques, often in combination, in real life. </P> <BR clear="left"> We'll tag each challenge to indicate which techniques are required to solve them: <BR><BR> <IMG src="/static/cheese_b.png" style="vertical-align:middle; padding-right: 5px;"> Challenges that can be solved just by using black box techniques.<BR><BR> <IMG src="/static/cheese_w.png" style="vertical-align:middle; padding-right: 5px;"> Challenges that require that you look at the Gruyere source code.<BR><BR> <IMG src="/static/cheese_bw.png" style="vertical-align:middle; padding-right: 5px;"> Challenges that require some specific knowledge of Gruyere that will be given in the first hint. <BR> <P style="color:red"> <B>WARNING:</B> Accessing or attacking a computer system without authorization is illegal in many jurisdictions. While doing this codelab, you are specifically granted authorization to attack the Gruyere application as directed. You may not attack Gruyere in ways other than described in this codelab, nor may you attack App Engine directly or any other Google service. You should use what you learn from the codelab to make your own applications more secure. You should not use it to attack any applications other than your own, and only do that with permission from the appropriate authorities (e.g., your company's security team). </P> <P></P> <FONT SIZE="+2"> <A href="/part1">Continue >></A> </FONT><BR> <BR> <P style="font-size:x-small"> © Google 2017 <A href="https://www.google.com/intl/en/policies/terms/">Terms of Service</A> <BR>The code portions of this codelab are licensed under the Creative Commons Attribution-No Derivative Works 3.0 United States license <<A href="https://creativecommons.org/licenses/by-nd/3.0/us/">https://creativecommons.org/licenses/by-nd/3.0/us</A>>. Brief excerpts of the code may be used for educational or instructional purposes provided this notice is kept intact. Except as otherwise noted the remainder of this codelab is licensed under the Creative Commons Attribution 3.0 United States license <<A href="https://creativecommons.org/licenses/by/3.0/us/">https://creativecommons.org/licenses/by/3.0/us</A>>. </P> </BODY></HTML> |
| URL | http://google-gruyere.appspot.com/0 |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 309 bytes. |
GET http://google-gruyere.appspot.com/0 HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/robots.txt |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 242 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: 31acf39338cca4c13a8ac0ebd8b4f695 Date: Tue, 06 Feb 2024 17:42:17 GMT Server: Google Frontend Content-Length: 359 |
| Response Body - size: 359 bytes. |
<HTML>
<STYLE> body, th, td, form { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; } h1 { color: #dd0000; } </STYLE> <TITLE>Gruyere Error</TITLE> <BODY> <H1>Gruyere Error</H1> That instance does not exist. <H2><A href="/">Home</A></H2> <H2><A href="/start">Start</A></H2> </BODY></HTML> |
| URL | http://google-gruyere.appspot.com/1 |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 309 bytes. |
GET http://google-gruyere.appspot.com/1 HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/robots.txt |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 242 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: 2de0e739188bfb4c756cf04d66dc15e4 Date: Tue, 06 Feb 2024 17:42:17 GMT Server: Google Frontend Content-Length: 359 |
| Response Body - size: 359 bytes. |
<HTML>
<STYLE> body, th, td, form { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; } h1 { color: #dd0000; } </STYLE> <TITLE>Gruyere Error</TITLE> <BODY> <H1>Gruyere Error</H1> That instance does not exist. <H2><A href="/">Home</A></H2> <H2><A href="/start">Start</A></H2> </BODY></HTML> |
| URL | http://google-gruyere.appspot.com/2 |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 309 bytes. |
GET http://google-gruyere.appspot.com/2 HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/robots.txt |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 242 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: 7eca5fc23f8b9884f93ce97b570b8646 Date: Tue, 06 Feb 2024 17:42:17 GMT Server: Google Frontend Content-Length: 359 |
| Response Body - size: 359 bytes. |
<HTML>
<STYLE> body, th, td, form { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; } h1 { color: #dd0000; } </STYLE> <TITLE>Gruyere Error</TITLE> <BODY> <H1>Gruyere Error</H1> That instance does not exist. <H2><A href="/">Home</A></H2> <H2><A href="/start">Start</A></H2> </BODY></HTML> |
| URL | http://google-gruyere.appspot.com/3 |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 309 bytes. |
GET http://google-gruyere.appspot.com/3 HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/robots.txt |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 242 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: 9ca17eedf0e86ef13ce8d268daca12dc Date: Tue, 06 Feb 2024 17:42:17 GMT Server: Google Frontend Content-Length: 359 |
| Response Body - size: 359 bytes. |
<HTML>
<STYLE> body, th, td, form { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; } h1 { color: #dd0000; } </STYLE> <TITLE>Gruyere Error</TITLE> <BODY> <H1>Gruyere Error</H1> That instance does not exist. <H2><A href="/">Home</A></H2> <H2><A href="/start">Start</A></H2> </BODY></HTML> |
| URL | http://google-gruyere.appspot.com/382665580745386307547168512335551204731/ |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 437 bytes. |
GET http://google-gruyere.appspot.com/382665580745386307547168512335551204731/ HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/382665580745386307547168512335551204731 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 249 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: afb44ad6606035ae60709b681130064c Date: Tue, 06 Feb 2024 17:42:21 GMT Server: Google Frontend Content-Length: 3480 |
| Response Body - size: 3,480 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Home</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> <script src="/382665580745386307547168512335551204731/lib.js" text="text/javascript"> </script> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/382665580745386307547168512335551204731/'>Home</a> </span> <span id='menu-right'> <a href='/382665580745386307547168512335551204731/login'>Sign in</a> | <a href='/382665580745386307547168512335551204731/newaccount.gtl'>Sign up</a> </span> </div> <div> <h2 class='has-refresh'>Gruyere: Home</h2> <div class='refresh'><a class='button' onclick='_refreshHome("382665580745386307547168512335551204731")' href='#'>Refresh</a></div> </div> <div class='content'> <table width='100%'> <tr><td colspan='3'><b>Most recent snippets:</b></td></tr> <tr> <td> </td> <td> <b><span style='color:blue'>Cheddar Mac</span></b> </td> <td width='100%'><span id='cheddar'>Gruyere is the cheesiest application on the web.</span> <br> <a href='/382665580745386307547168512335551204731/snippets.gtl?uid=cheddar'>All snippets</a> <a href='https://images.google.com/?q=cheddar+cheese'>Homepage</a> <br> <br> </td> </tr> <tr> <td> </td> <td> <b><span style='color:red; text-decoration:underline'>Brie</span></b> </td> <td width='100%'><span id='brie'>Brie is the queen of the cheeses<span style=color:red>!!!</span></span> <br> <a href='/382665580745386307547168512335551204731/snippets.gtl?uid=brie'>All snippets</a> <a href='https://news.google.com/news/search?q=brie'>Homepage</a> <br> <br> </td> </tr> </table> </div> </body> </html> |
| URL | http://google-gruyere.appspot.com/382665580745386307547168512335551204731/login |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 443 bytes. |
GET http://google-gruyere.appspot.com/382665580745386307547168512335551204731/login HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/382665580745386307547168512335551204731/ Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 249 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: 16b439f06534331a20b8802d36a3b6fa Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 2591 |
| Response Body - size: 2,591 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Login</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/382665580745386307547168512335551204731/'>Home</a> </span> <span id='menu-right'> <a href='/382665580745386307547168512335551204731/login'>Sign in</a> | <a href='/382665580745386307547168512335551204731/newaccount.gtl'>Sign up</a> </span> </div> <div> <h2>Gruyere: Login</h2> </div> <div class='content'> <form method='get' action='/382665580745386307547168512335551204731/login'> <table><tr><td> User name: </td><td> <input type='text' name='uid'> </td></tr><tr><td> Password: </td><td> <input type='password' name='pw'> </td></tr><tr><td></td><td align='right'> <input type='submit' value='Login'> </td></tr></table> </form> </div> </body> </html> |
| URL | http://google-gruyere.appspot.com/382665580745386307547168512335551204731/login?pw=ZAP&uid=ZAP |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 463 bytes. |
GET http://google-gruyere.appspot.com/382665580745386307547168512335551204731/login?pw=ZAP&uid=ZAP HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/382665580745386307547168512335551204731/login Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 249 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: 1b0b3f007d98bdce40097f85480aebda Date: Tue, 06 Feb 2024 17:42:25 GMT Server: Google Frontend Content-Length: 2650 |
| Response Body - size: 2,650 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Login</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/382665580745386307547168512335551204731/'>Home</a> </span> <span id='menu-right'> <a href='/382665580745386307547168512335551204731/login'>Sign in</a> | <a href='/382665580745386307547168512335551204731/newaccount.gtl'>Sign up</a> </span> </div> <div> <h2>Gruyere: Login</h2> </div> <div class='message'>Invalid user name or password.</div> <div class='content'> <form method='get' action='/382665580745386307547168512335551204731/login'> <table><tr><td> User name: </td><td> <input type='text' name='uid'> </td></tr><tr><td> Password: </td><td> <input type='password' name='pw'> </td></tr><tr><td></td><td align='right'> <input type='submit' value='Login'> </td></tr></table> </form> </div> </body> </html> |
| URL | http://google-gruyere.appspot.com/382665580745386307547168512335551204731/newaccount.gtl |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 452 bytes. |
GET http://google-gruyere.appspot.com/382665580745386307547168512335551204731/newaccount.gtl HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/382665580745386307547168512335551204731/ Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 249 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: 81fd781cfd9ee13ea54b5718c50baeed Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 2961 |
| Response Body - size: 2,961 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Profile</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/382665580745386307547168512335551204731/'>Home</a> </span> <span id='menu-right'> <a href='/382665580745386307547168512335551204731/login'>Sign in</a> | <a href='/382665580745386307547168512335551204731/newaccount.gtl'>Sign up</a> </span> </div> <h2>Gruyere: Sign up</h2> <div class='content'> <h3>Sign up for a new account.</h3> <form method='get' action='/382665580745386307547168512335551204731/saveprofile'> <input type='hidden' name='action' value='new'> <table><tr><td> User name: </td><td> <input type='text' name='uid' value='' maxlength='16'> </td></tr> <tr><td> Password: </td><td> <input type='password' name='pw'> <br><span style="color:red"><b>WARNING: Gruyere is not secure.<br> Do not use a password that you use for any real service.<br> Do not upload any personal or private data.</b></span> </td></tr> <input type='hidden' name='is_author' value='True'> <tr><td></td><td align='right'> <input type='submit' value='Create account'> </td></tr> </table> </form> </div> </body> </html> |
| URL | http://google-gruyere.appspot.com/382665580745386307547168512335551204731/saveprofile?action=new&is_author=True&pw=ZAP&uid=ZAP |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 504 bytes. |
GET http://google-gruyere.appspot.com/382665580745386307547168512335551204731/saveprofile?action=new&is_author=True&pw=ZAP&uid=ZAP HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/382665580745386307547168512335551204731/newaccount.gtl Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 378 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache Set-Cookie: GRUYERE=26087470|ZAP||author; path=/382665580745386307547168512335551204731 X-XSS-Protection: 0 X-Cloud-Trace-Context: 89a603bf4f00caebb0a57d884f1f61e1 Date: Tue, 06 Feb 2024 17:42:25 GMT Server: Google Frontend Content-Length: 2224 Expires: Tue, 06 Feb 2024 17:42:25 GMT |
| Response Body - size: 2,224 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Error</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/382665580745386307547168512335551204731/'>Home</a> </span> <span id='menu-right'> <a href='/382665580745386307547168512335551204731/login'>Sign in</a> | <a href='/382665580745386307547168512335551204731/newaccount.gtl'>Sign up</a> </span> </div> <div class='message'>Account created.</div> </body> </html> |
| URL | http://google-gruyere.appspot.com/382665580745386307547168512335551204731/snippets.gtl?uid=brie |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 459 bytes. |
GET http://google-gruyere.appspot.com/382665580745386307547168512335551204731/snippets.gtl?uid=brie HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/382665580745386307547168512335551204731/ Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 249 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: 572637f34b475afec4702be8a9453c2f Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 3059 |
| Response Body - size: 3,059 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Snippets</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> <script src="/382665580745386307547168512335551204731/lib.js" text="text/javascript"> </script> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/382665580745386307547168512335551204731/'>Home</a> </span> <span id='menu-right'> <a href='/382665580745386307547168512335551204731/login'>Sign in</a> | <a href='/382665580745386307547168512335551204731/newaccount.gtl'>Sign up</a> </span> </div> <div> <h2 class='has-refresh' id="user_name"> Brie </h2> <div class='refresh'><a class='button' onclick='_refreshSnippets("382665580745386307547168512335551204731", "brie")' href='#'>Refresh</a></div> <div class='content'> <table> <tr><td colspan='2'><b>All snippets:</b></td></tr> <tr> <td valign='top'> <script>document.write(0 + 1)</script> </td> <td valign='top'> <div id='0'> Brie is the queen of the cheeses<span style=color:red>!!!</span> </div> </td> </tr> </table> <br> <a href='https://news.google.com/news/search?q=brie'>Brie's site</a> </div> </body> </html> |
| URL | http://google-gruyere.appspot.com/382665580745386307547168512335551204731/snippets.gtl?uid=cheddar |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 462 bytes. |
GET http://google-gruyere.appspot.com/382665580745386307547168512335551204731/snippets.gtl?uid=cheddar HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/382665580745386307547168512335551204731/ Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 249 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: 68a11750c5367b4fae4aa3742510fd60 Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 3379 |
| Response Body - size: 3,379 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Snippets</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> <script src="/382665580745386307547168512335551204731/lib.js" text="text/javascript"> </script> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/382665580745386307547168512335551204731/'>Home</a> </span> <span id='menu-right'> <a href='/382665580745386307547168512335551204731/login'>Sign in</a> | <a href='/382665580745386307547168512335551204731/newaccount.gtl'>Sign up</a> </span> </div> <div> <h2 class='has-refresh' id="user_name"> Cheddar Mac </h2> <div class='refresh'><a class='button' onclick='_refreshSnippets("382665580745386307547168512335551204731", "cheddar")' href='#'>Refresh</a></div> <div class='content'> <table> <tr><td colspan='2'><b>All snippets:</b></td></tr> <tr> <td valign='top'> <script>document.write(0 + 1)</script> </td> <td valign='top'> <div id='0'> Gruyere is the cheesiest application on the web. </div> </td> </tr> <tr> <td valign='top'> <script>document.write(1 + 1)</script> </td> <td valign='top'> <div id='1'> I wonder if there are any security holes in this.... </div> </td> </tr> </table> <br> <a href='https://images.google.com/?q=cheddar+cheese'>Cheddar Mac's site</a> </div> </body> </html> |
| URL | http://google-gruyere.appspot.com/397587545265662818066921574239585949762/ |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 293 bytes. |
GET http://google-gruyere.appspot.com/397587545265662818066921574239585949762/ HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 249 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: a28a257ef79b06cf248b909efdc7c4c9 Date: Tue, 06 Feb 2024 17:42:17 GMT Server: Google Frontend Content-Length: 3480 |
| Response Body - size: 3,480 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Home</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> <script src="/397587545265662818066921574239585949762/lib.js" text="text/javascript"> </script> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/397587545265662818066921574239585949762/'>Home</a> </span> <span id='menu-right'> <a href='/397587545265662818066921574239585949762/login'>Sign in</a> | <a href='/397587545265662818066921574239585949762/newaccount.gtl'>Sign up</a> </span> </div> <div> <h2 class='has-refresh'>Gruyere: Home</h2> <div class='refresh'><a class='button' onclick='_refreshHome("397587545265662818066921574239585949762")' href='#'>Refresh</a></div> </div> <div class='content'> <table width='100%'> <tr><td colspan='3'><b>Most recent snippets:</b></td></tr> <tr> <td> </td> <td> <b><span style='color:blue'>Cheddar Mac</span></b> </td> <td width='100%'><span id='cheddar'>Gruyere is the cheesiest application on the web.</span> <br> <a href='/397587545265662818066921574239585949762/snippets.gtl?uid=cheddar'>All snippets</a> <a href='https://images.google.com/?q=cheddar+cheese'>Homepage</a> <br> <br> </td> </tr> <tr> <td> </td> <td> <b><span style='color:red; text-decoration:underline'>Brie</span></b> </td> <td width='100%'><span id='brie'>Brie is the queen of the cheeses<span style=color:red>!!!</span></span> <br> <a href='/397587545265662818066921574239585949762/snippets.gtl?uid=brie'>All snippets</a> <a href='https://news.google.com/news/search?q=brie'>Homepage</a> <br> <br> </td> </tr> </table> </div> </body> </html> |
| URL | http://google-gruyere.appspot.com/397587545265662818066921574239585949762/login |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 383 bytes. |
GET http://google-gruyere.appspot.com/397587545265662818066921574239585949762/login HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/397587545265662818066921574239585949762/ |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 249 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: 9083b154ecc26c292a9c737c8fa7a655 Date: Tue, 06 Feb 2024 17:42:17 GMT Server: Google Frontend Content-Length: 2591 |
| Response Body - size: 2,591 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Login</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/397587545265662818066921574239585949762/'>Home</a> </span> <span id='menu-right'> <a href='/397587545265662818066921574239585949762/login'>Sign in</a> | <a href='/397587545265662818066921574239585949762/newaccount.gtl'>Sign up</a> </span> </div> <div> <h2>Gruyere: Login</h2> </div> <div class='content'> <form method='get' action='/397587545265662818066921574239585949762/login'> <table><tr><td> User name: </td><td> <input type='text' name='uid'> </td></tr><tr><td> Password: </td><td> <input type='password' name='pw'> </td></tr><tr><td></td><td align='right'> <input type='submit' value='Login'> </td></tr></table> </form> </div> </body> </html> |
| URL | http://google-gruyere.appspot.com/397587545265662818066921574239585949762/login?pw=ZAP&uid=ZAP |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 463 bytes. |
GET http://google-gruyere.appspot.com/397587545265662818066921574239585949762/login?pw=ZAP&uid=ZAP HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/397587545265662818066921574239585949762/login Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 249 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: 9899d637fd9ca7dddfd483f9210096d5 Date: Tue, 06 Feb 2024 17:42:18 GMT Server: Google Frontend Content-Length: 2650 |
| Response Body - size: 2,650 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Login</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/397587545265662818066921574239585949762/'>Home</a> </span> <span id='menu-right'> <a href='/397587545265662818066921574239585949762/login'>Sign in</a> | <a href='/397587545265662818066921574239585949762/newaccount.gtl'>Sign up</a> </span> </div> <div> <h2>Gruyere: Login</h2> </div> <div class='message'>Invalid user name or password.</div> <div class='content'> <form method='get' action='/397587545265662818066921574239585949762/login'> <table><tr><td> User name: </td><td> <input type='text' name='uid'> </td></tr><tr><td> Password: </td><td> <input type='password' name='pw'> </td></tr><tr><td></td><td align='right'> <input type='submit' value='Login'> </td></tr></table> </form> </div> </body> </html> |
| URL | http://google-gruyere.appspot.com/397587545265662818066921574239585949762/newaccount.gtl |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 392 bytes. |
GET http://google-gruyere.appspot.com/397587545265662818066921574239585949762/newaccount.gtl HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/397587545265662818066921574239585949762/ |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 249 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: a9898974a14997c3d97dfe9c098c8645 Date: Tue, 06 Feb 2024 17:42:18 GMT Server: Google Frontend Content-Length: 2961 |
| Response Body - size: 2,961 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Profile</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/397587545265662818066921574239585949762/'>Home</a> </span> <span id='menu-right'> <a href='/397587545265662818066921574239585949762/login'>Sign in</a> | <a href='/397587545265662818066921574239585949762/newaccount.gtl'>Sign up</a> </span> </div> <h2>Gruyere: Sign up</h2> <div class='content'> <h3>Sign up for a new account.</h3> <form method='get' action='/397587545265662818066921574239585949762/saveprofile'> <input type='hidden' name='action' value='new'> <table><tr><td> User name: </td><td> <input type='text' name='uid' value='' maxlength='16'> </td></tr> <tr><td> Password: </td><td> <input type='password' name='pw'> <br><span style="color:red"><b>WARNING: Gruyere is not secure.<br> Do not use a password that you use for any real service.<br> Do not upload any personal or private data.</b></span> </td></tr> <input type='hidden' name='is_author' value='True'> <tr><td></td><td align='right'> <input type='submit' value='Create account'> </td></tr> </table> </form> </div> </body> </html> |
| URL | http://google-gruyere.appspot.com/397587545265662818066921574239585949762/saveprofile?action=new&is_author=True&pw=ZAP&uid=ZAP |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 504 bytes. |
GET http://google-gruyere.appspot.com/397587545265662818066921574239585949762/saveprofile?action=new&is_author=True&pw=ZAP&uid=ZAP HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/397587545265662818066921574239585949762/newaccount.gtl Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 378 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache Set-Cookie: GRUYERE=26087470|ZAP||author; path=/397587545265662818066921574239585949762 X-XSS-Protection: 0 X-Cloud-Trace-Context: 88931a38df2e52e671b2c4f9f074850c Date: Tue, 06 Feb 2024 17:42:18 GMT Server: Google Frontend Content-Length: 2224 Expires: Tue, 06 Feb 2024 17:42:18 GMT |
| Response Body - size: 2,224 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Error</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/397587545265662818066921574239585949762/'>Home</a> </span> <span id='menu-right'> <a href='/397587545265662818066921574239585949762/login'>Sign in</a> | <a href='/397587545265662818066921574239585949762/newaccount.gtl'>Sign up</a> </span> </div> <div class='message'>Account created.</div> </body> </html> |
| URL | http://google-gruyere.appspot.com/397587545265662818066921574239585949762/snippets.gtl?uid=brie |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 459 bytes. |
GET http://google-gruyere.appspot.com/397587545265662818066921574239585949762/snippets.gtl?uid=brie HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/397587545265662818066921574239585949762/ Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 249 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: 580704941b98bc2503ec3725400623e3 Date: Tue, 06 Feb 2024 17:42:18 GMT Server: Google Frontend Content-Length: 3059 |
| Response Body - size: 3,059 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Snippets</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> <script src="/397587545265662818066921574239585949762/lib.js" text="text/javascript"> </script> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/397587545265662818066921574239585949762/'>Home</a> </span> <span id='menu-right'> <a href='/397587545265662818066921574239585949762/login'>Sign in</a> | <a href='/397587545265662818066921574239585949762/newaccount.gtl'>Sign up</a> </span> </div> <div> <h2 class='has-refresh' id="user_name"> Brie </h2> <div class='refresh'><a class='button' onclick='_refreshSnippets("397587545265662818066921574239585949762", "brie")' href='#'>Refresh</a></div> <div class='content'> <table> <tr><td colspan='2'><b>All snippets:</b></td></tr> <tr> <td valign='top'> <script>document.write(0 + 1)</script> </td> <td valign='top'> <div id='0'> Brie is the queen of the cheeses<span style=color:red>!!!</span> </div> </td> </tr> </table> <br> <a href='https://news.google.com/news/search?q=brie'>Brie's site</a> </div> </body> </html> |
| URL | http://google-gruyere.appspot.com/397587545265662818066921574239585949762/snippets.gtl?uid=cheddar |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 402 bytes. |
GET http://google-gruyere.appspot.com/397587545265662818066921574239585949762/snippets.gtl?uid=cheddar HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/397587545265662818066921574239585949762/ |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 249 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: a11c4b524f2923326c98c963afe3771c Date: Tue, 06 Feb 2024 17:42:18 GMT Server: Google Frontend Content-Length: 3379 |
| Response Body - size: 3,379 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Snippets</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> <script src="/397587545265662818066921574239585949762/lib.js" text="text/javascript"> </script> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/397587545265662818066921574239585949762/'>Home</a> </span> <span id='menu-right'> <a href='/397587545265662818066921574239585949762/login'>Sign in</a> | <a href='/397587545265662818066921574239585949762/newaccount.gtl'>Sign up</a> </span> </div> <div> <h2 class='has-refresh' id="user_name"> Cheddar Mac </h2> <div class='refresh'><a class='button' onclick='_refreshSnippets("397587545265662818066921574239585949762", "cheddar")' href='#'>Refresh</a></div> <div class='content'> <table> <tr><td colspan='2'><b>All snippets:</b></td></tr> <tr> <td valign='top'> <script>document.write(0 + 1)</script> </td> <td valign='top'> <div id='0'> Gruyere is the cheesiest application on the web. </div> </td> </tr> <tr> <td valign='top'> <script>document.write(1 + 1)</script> </td> <td valign='top'> <div id='1'> I wonder if there are any security holes in this.... </div> </td> </tr> </table> <br> <a href='https://images.google.com/?q=cheddar+cheese'>Cheddar Mac's site</a> </div> </body> </html> |
| URL | http://google-gruyere.appspot.com/4 |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 309 bytes. |
GET http://google-gruyere.appspot.com/4 HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/robots.txt |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 242 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: 6b6c1f5e5992f5526d08b5733ab01efa Date: Tue, 06 Feb 2024 17:42:17 GMT Server: Google Frontend Content-Length: 359 |
| Response Body - size: 359 bytes. |
<HTML>
<STYLE> body, th, td, form { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; } h1 { color: #dd0000; } </STYLE> <TITLE>Gruyere Error</TITLE> <BODY> <H1>Gruyere Error</H1> That instance does not exist. <H2><A href="/">Home</A></H2> <H2><A href="/start">Start</A></H2> </BODY></HTML> |
| URL | http://google-gruyere.appspot.com/5 |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 309 bytes. |
GET http://google-gruyere.appspot.com/5 HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/robots.txt |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 242 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: 9a098e9e493538fe45dc4b5b8bc86c90 Date: Tue, 06 Feb 2024 17:42:17 GMT Server: Google Frontend Content-Length: 359 |
| Response Body - size: 359 bytes. |
<HTML>
<STYLE> body, th, td, form { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; } h1 { color: #dd0000; } </STYLE> <TITLE>Gruyere Error</TITLE> <BODY> <H1>Gruyere Error</H1> That instance does not exist. <H2><A href="/">Home</A></H2> <H2><A href="/start">Start</A></H2> </BODY></HTML> |
| URL | http://google-gruyere.appspot.com/6 |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 309 bytes. |
GET http://google-gruyere.appspot.com/6 HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/robots.txt |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 242 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: ffd1d5503f0612cb50d92820b7e973c0 Date: Tue, 06 Feb 2024 17:42:17 GMT Server: Google Frontend Content-Length: 359 |
| Response Body - size: 359 bytes. |
<HTML>
<STYLE> body, th, td, form { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; } h1 { color: #dd0000; } </STYLE> <TITLE>Gruyere Error</TITLE> <BODY> <H1>Gruyere Error</H1> That instance does not exist. <H2><A href="/">Home</A></H2> <H2><A href="/start">Start</A></H2> </BODY></HTML> |
| URL | http://google-gruyere.appspot.com/7 |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 309 bytes. |
GET http://google-gruyere.appspot.com/7 HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/robots.txt |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 242 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: c4c66498c12d026492197ab71fb6f619 Date: Tue, 06 Feb 2024 17:42:17 GMT Server: Google Frontend Content-Length: 359 |
| Response Body - size: 359 bytes. |
<HTML>
<STYLE> body, th, td, form { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; } h1 { color: #dd0000; } </STYLE> <TITLE>Gruyere Error</TITLE> <BODY> <H1>Gruyere Error</H1> That instance does not exist. <H2><A href="/">Home</A></H2> <H2><A href="/start">Start</A></H2> </BODY></HTML> |
| URL | http://google-gruyere.appspot.com/8 |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 309 bytes. |
GET http://google-gruyere.appspot.com/8 HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/robots.txt |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 242 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: d267fca285d0f89f6f33f8e6dd1381b1 Date: Tue, 06 Feb 2024 17:42:17 GMT Server: Google Frontend Content-Length: 359 |
| Response Body - size: 359 bytes. |
<HTML>
<STYLE> body, th, td, form { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; } h1 { color: #dd0000; } </STYLE> <TITLE>Gruyere Error</TITLE> <BODY> <H1>Gruyere Error</H1> That instance does not exist. <H2><A href="/">Home</A></H2> <H2><A href="/start">Start</A></H2> </BODY></HTML> |
| URL | http://google-gruyere.appspot.com/9 |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 309 bytes. |
GET http://google-gruyere.appspot.com/9 HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/robots.txt |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 242 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: c607b5cf2b147b18d0d00d483fec7990 Date: Tue, 06 Feb 2024 17:42:17 GMT Server: Google Frontend Content-Length: 359 |
| Response Body - size: 359 bytes. |
<HTML>
<STYLE> body, th, td, form { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; } h1 { color: #dd0000; } </STYLE> <TITLE>Gruyere Error</TITLE> <BODY> <H1>Gruyere Error</H1> That instance does not exist. <H2><A href="/">Home</A></H2> <H2><A href="/start">Start</A></H2> </BODY></HTML> |
| URL | http://google-gruyere.appspot.com/code/ |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 368 bytes. |
GET http://google-gruyere.appspot.com/code/ HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/part1 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 242 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: a2ac29924b94533ea4fb5dd794718202 Date: Tue, 06 Feb 2024 17:42:21 GMT Server: Google Frontend Content-Length: 354 |
| Response Body - size: 354 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN"
"https://www.w3.org/TR/html4/frameset.dtd"> <HTML> <HEAD> <TITLE>Gruyere Code</TITLE> </HEAD> <FRAMESET cols="165,100%"> <FRAME src="/static/codeindex.html"> <FRAME name="codepage" src=""> <NOFRAMES> <A href="/static/codeindex/html">Code Index</A> </NOFRAMES> </FRAMESET> </HTML> |
| URL | http://google-gruyere.appspot.com/code/?data.py |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 376 bytes. |
GET http://google-gruyere.appspot.com/code/?data.py HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/part1 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 242 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: dd7164b7280debdede68a6e298f79233 Date: Tue, 06 Feb 2024 17:42:21 GMT Server: Google Frontend Content-Length: 367 |
| Response Body - size: 367 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN"
"https://www.w3.org/TR/html4/frameset.dtd"> <HTML> <HEAD> <TITLE>Gruyere Code</TITLE> </HEAD> <FRAMESET cols="165,100%"> <FRAME src="/static/codeindex.html"> <FRAME name="codepage" src="/code/data.py"> <NOFRAMES> <A href="/static/codeindex/html">Code Index</A> </NOFRAMES> </FRAMESET> </HTML> |
| URL | http://google-gruyere.appspot.com/code/?gruyere.py |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 379 bytes. |
GET http://google-gruyere.appspot.com/code/?gruyere.py HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/part1 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 242 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: 5586748370261caee1bd94c9469b4724 Date: Tue, 06 Feb 2024 17:42:21 GMT Server: Google Frontend Content-Length: 370 |
| Response Body - size: 370 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN"
"https://www.w3.org/TR/html4/frameset.dtd"> <HTML> <HEAD> <TITLE>Gruyere Code</TITLE> </HEAD> <FRAMESET cols="165,100%"> <FRAME src="/static/codeindex.html"> <FRAME name="codepage" src="/code/gruyere.py"> <NOFRAMES> <A href="/static/codeindex/html">Code Index</A> </NOFRAMES> </FRAMESET> </HTML> |
| URL | http://google-gruyere.appspot.com/code/?gtl.py |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 375 bytes. |
GET http://google-gruyere.appspot.com/code/?gtl.py HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/part1 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 242 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: a71b76cc1d5c4be2212e2fc0ab83c53b Date: Tue, 06 Feb 2024 17:42:22 GMT Server: Google Frontend Content-Length: 366 |
| Response Body - size: 366 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN"
"https://www.w3.org/TR/html4/frameset.dtd"> <HTML> <HEAD> <TITLE>Gruyere Code</TITLE> </HEAD> <FRAMESET cols="165,100%"> <FRAME src="/static/codeindex.html"> <FRAME name="codepage" src="/code/gtl.py"> <NOFRAMES> <A href="/static/codeindex/html">Code Index</A> </NOFRAMES> </FRAMESET> </HTML> |
| URL | http://google-gruyere.appspot.com/code/?resoources/dump.gtl |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 388 bytes. |
GET http://google-gruyere.appspot.com/code/?resoources/dump.gtl HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/part5 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 242 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: e5ef295042883012b5ba3222bc3ac891 Date: Tue, 06 Feb 2024 17:42:23 GMT Server: Google Frontend Content-Length: 354 |
| Response Body - size: 354 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN"
"https://www.w3.org/TR/html4/frameset.dtd"> <HTML> <HEAD> <TITLE>Gruyere Code</TITLE> </HEAD> <FRAMESET cols="165,100%"> <FRAME src="/static/codeindex.html"> <FRAME name="codepage" src=""> <NOFRAMES> <A href="/static/codeindex/html">Code Index</A> </NOFRAMES> </FRAMESET> </HTML> |
| URL | http://google-gruyere.appspot.com/code/?resources/dump.gtl |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 387 bytes. |
GET http://google-gruyere.appspot.com/code/?resources/dump.gtl HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/part5 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 242 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: 7b25ef14221c561f20db288ba5d3ced0 Date: Tue, 06 Feb 2024 17:42:23 GMT Server: Google Frontend Content-Length: 378 |
| Response Body - size: 378 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN"
"https://www.w3.org/TR/html4/frameset.dtd"> <HTML> <HEAD> <TITLE>Gruyere Code</TITLE> </HEAD> <FRAMESET cols="165,100%"> <FRAME src="/static/codeindex.html"> <FRAME name="codepage" src="/code/resources/dump.gtl"> <NOFRAMES> <A href="/static/codeindex/html">Code Index</A> </NOFRAMES> </FRAMESET> </HTML> |
| URL | http://google-gruyere.appspot.com/code/?resources/editprofile.gtl |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 394 bytes. |
GET http://google-gruyere.appspot.com/code/?resources/editprofile.gtl HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/part3 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 242 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: 329a33fddd017ee001084efe67d3396c Date: Tue, 06 Feb 2024 17:42:23 GMT Server: Google Frontend Content-Length: 385 |
| Response Body - size: 385 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN"
"https://www.w3.org/TR/html4/frameset.dtd"> <HTML> <HEAD> <TITLE>Gruyere Code</TITLE> </HEAD> <FRAMESET cols="165,100%"> <FRAME src="/static/codeindex.html"> <FRAME name="codepage" src="/code/resources/editprofile.gtl"> <NOFRAMES> <A href="/static/codeindex/html">Code Index</A> </NOFRAMES> </FRAMESET> </HTML> |
| URL | http://google-gruyere.appspot.com/code/?resources/error.gtl |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 388 bytes. |
GET http://google-gruyere.appspot.com/code/?resources/error.gtl HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/part2 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 242 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: da4f492d706e701689807f8475a10dec Date: Tue, 06 Feb 2024 17:42:23 GMT Server: Google Frontend Content-Length: 379 |
| Response Body - size: 379 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN"
"https://www.w3.org/TR/html4/frameset.dtd"> <HTML> <HEAD> <TITLE>Gruyere Code</TITLE> </HEAD> <FRAMESET cols="165,100%"> <FRAME src="/static/codeindex.html"> <FRAME name="codepage" src="/code/resources/error.gtl"> <NOFRAMES> <A href="/static/codeindex/html">Code Index</A> </NOFRAMES> </FRAMESET> </HTML> |
| URL | http://google-gruyere.appspot.com/code/?resources/feed.gtl |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 387 bytes. |
GET http://google-gruyere.appspot.com/code/?resources/feed.gtl HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/part1 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 242 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: 1c4bbde7a962153bb90c276f1875dd0b Date: Tue, 06 Feb 2024 17:42:22 GMT Server: Google Frontend Content-Length: 378 |
| Response Body - size: 378 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN"
"https://www.w3.org/TR/html4/frameset.dtd"> <HTML> <HEAD> <TITLE>Gruyere Code</TITLE> </HEAD> <FRAMESET cols="165,100%"> <FRAME src="/static/codeindex.html"> <FRAME name="codepage" src="/code/resources/feed.gtl"> <NOFRAMES> <A href="/static/codeindex/html">Code Index</A> </NOFRAMES> </FRAMESET> </HTML> |
| URL | http://google-gruyere.appspot.com/code/?resources/home.gtl |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 387 bytes. |
GET http://google-gruyere.appspot.com/code/?resources/home.gtl HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/part2 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 242 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: 5375fd055e641e63a7b3b7a869ce9082 Date: Tue, 06 Feb 2024 17:42:23 GMT Server: Google Frontend Content-Length: 378 |
| Response Body - size: 378 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN"
"https://www.w3.org/TR/html4/frameset.dtd"> <HTML> <HEAD> <TITLE>Gruyere Code</TITLE> </HEAD> <FRAMESET cols="165,100%"> <FRAME src="/static/codeindex.html"> <FRAME name="codepage" src="/code/resources/home.gtl"> <NOFRAMES> <A href="/static/codeindex/html">Code Index</A> </NOFRAMES> </FRAMESET> </HTML> |
| URL | http://google-gruyere.appspot.com/code/?resources/manage.gtl |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 389 bytes. |
GET http://google-gruyere.appspot.com/code/?resources/manage.gtl HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/part4 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 242 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: d11704238467f7d2308117ac035b9b43 Date: Tue, 06 Feb 2024 17:42:23 GMT Server: Google Frontend Content-Length: 380 |
| Response Body - size: 380 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN"
"https://www.w3.org/TR/html4/frameset.dtd"> <HTML> <HEAD> <TITLE>Gruyere Code</TITLE> </HEAD> <FRAMESET cols="165,100%"> <FRAME src="/static/codeindex.html"> <FRAME name="codepage" src="/code/resources/manage.gtl"> <NOFRAMES> <A href="/static/codeindex/html">Code Index</A> </NOFRAMES> </FRAMESET> </HTML> |
| URL | http://google-gruyere.appspot.com/code/?resources/menubar.gtl |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 390 bytes. |
GET http://google-gruyere.appspot.com/code/?resources/menubar.gtl HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/part4 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 242 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: a5e2f484f397f6415f3a4876284bb09e Date: Tue, 06 Feb 2024 17:42:23 GMT Server: Google Frontend Content-Length: 381 |
| Response Body - size: 381 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN"
"https://www.w3.org/TR/html4/frameset.dtd"> <HTML> <HEAD> <TITLE>Gruyere Code</TITLE> </HEAD> <FRAMESET cols="165,100%"> <FRAME src="/static/codeindex.html"> <FRAME name="codepage" src="/code/resources/menubar.gtl"> <NOFRAMES> <A href="/static/codeindex/html">Code Index</A> </NOFRAMES> </FRAMESET> </HTML> |
| URL | http://google-gruyere.appspot.com/code/?sanitize.py |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 380 bytes. |
GET http://google-gruyere.appspot.com/code/?sanitize.py HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/part1 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 242 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: e0dc5d0faacef6fef9cc9a73156dbc84 Date: Tue, 06 Feb 2024 17:42:22 GMT Server: Google Frontend Content-Length: 371 |
| Response Body - size: 371 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN"
"https://www.w3.org/TR/html4/frameset.dtd"> <HTML> <HEAD> <TITLE>Gruyere Code</TITLE> </HEAD> <FRAMESET cols="165,100%"> <FRAME src="/static/codeindex.html"> <FRAME name="codepage" src="/code/sanitize.py"> <NOFRAMES> <A href="/static/codeindex/html">Code Index</A> </NOFRAMES> </FRAMESET> </HTML> |
| URL | http://google-gruyere.appspot.com/code/data.py |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 383 bytes. |
GET http://google-gruyere.appspot.com/code/data.py HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/code/?data.py Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 243 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: b152b8ac64ff7ccb7b932ffaf58aa6d5 Date: Tue, 06 Feb 2024 17:42:23 GMT Server: Google Frontend Content-Length: 4190 |
| Response Body - size: 4,190 bytes. |
<HTML>
<STYLE> SMALL { color: #AAAAFF; font-size: 75% } BIG { color: #3333FF; font-size: 150%; font-weight: bold} </STYLE><BODY><PRE> <BIG>data.py</BIG> <SMALL> 1 </SMALL>"""Gruyere - Default data for Gruyere, a web application with holes. <SMALL> 2 </SMALL> <SMALL> 3 </SMALL>Copyright 2017 Google Inc. All rights reserved. <SMALL> 4 </SMALL> <SMALL> 5 </SMALL>This code is licensed under the https://creativecommons.org/licenses/by-nd/3.0/us/ <SMALL> 6 </SMALL>Creative Commons Attribution-No Derivative Works 3.0 United States license. <SMALL> 7 </SMALL> <SMALL> 8 </SMALL>DO NOT COPY THIS CODE! <SMALL> 9 </SMALL> <SMALL> 10 </SMALL>This application is a small self-contained web application with numerous <SMALL> 11 </SMALL>security holes. It is provided for use with the Web Application Exploits and <SMALL> 12 </SMALL>Defenses codelab. You may modify the code for your own use while doing the <SMALL> 13 </SMALL>codelab but you may not distribute the modified code. Brief excerpts of this <SMALL> 14 </SMALL>code may be used for educational or instructional purposes provided this <SMALL> 15 </SMALL>notice is kept intact. By using Gruyere you agree to the Terms of Service <SMALL> 16 </SMALL>https://www.google.com/intl/en/policies/terms/ <SMALL> 17 </SMALL>""" <SMALL> 18 </SMALL> <SMALL> 19 </SMALL>__author__ = 'Bruce Leban' <SMALL> 20 </SMALL> <SMALL> 21 </SMALL># system modules <SMALL> 22 </SMALL>import copy <SMALL> 23 </SMALL> <SMALL> 24 </SMALL>DEFAULT_DATA = { <SMALL> 25 </SMALL> 'administrator': { <SMALL> 26 </SMALL> 'name': 'Admin', <SMALL> 27 </SMALL> 'pw': 'secret', <SMALL> 28 </SMALL> 'is_author': False, <SMALL> 29 </SMALL> 'is_admin': True, <SMALL> 30 </SMALL> 'private_snippet': 'My password is secret. Get it?', <SMALL> 31 </SMALL> 'web_site': 'https://www.google.com/contact/', <SMALL> 32 </SMALL> }, <SMALL> 33 </SMALL> 'cheddar': { <SMALL> 34 </SMALL> 'name': 'Cheddar Mac', <SMALL> 35 </SMALL> 'pw': 'orange', <SMALL> 36 </SMALL> 'is_author': True, <SMALL> 37 </SMALL> 'is_admin': False, <SMALL> 38 </SMALL> 'private_snippet': 'My SSN is <a href="https://www.google.com/' + <SMALL> 39 </SMALL> 'search?q=078-05-1120">078-05-1120</a>.', <SMALL> 40 </SMALL> 'web_site': 'https://images.google.com/?q=cheddar+cheese', <SMALL> 41 </SMALL> 'color': 'blue', <SMALL> 42 </SMALL> 'snippets': [ <SMALL> 43 </SMALL> 'Gruyere is the cheesiest application on the web.', <SMALL> 44 </SMALL> 'I wonder if there are any security holes in this....' <SMALL> 45 </SMALL> ], <SMALL> 46 </SMALL> }, <SMALL> 47 </SMALL> 'sardo': { <SMALL> 48 </SMALL> 'name': 'Miss Sardo', <SMALL> 49 </SMALL> 'pw': 'odras', <SMALL> 50 </SMALL> 'is_author': True, <SMALL> 51 </SMALL> 'is_admin': False, <SMALL> 52 </SMALL> 'private_snippet': 'I hate my brother Romano.', <SMALL> 53 </SMALL> 'web_site': 'https://www.google.com/search?q="pecorino+sardo"', <SMALL> 54 </SMALL> 'color': 'red', <SMALL> 55 </SMALL> 'snippets': [], <SMALL> 56 </SMALL> }, <SMALL> 57 </SMALL> 'brie': { <SMALL> 58 </SMALL> 'name': 'Brie', <SMALL> 59 </SMALL> 'pw': 'briebrie', <SMALL> 60 </SMALL> 'is_author': True, <SMALL> 61 </SMALL> 'is_admin': False, <SMALL> 62 </SMALL> 'private_snippet': 'I use the same password for all my accounts.', <SMALL> 63 </SMALL> 'web_site': 'https://news.google.com/news/search?q=brie', <SMALL> 64 </SMALL> 'color': 'red; text-decoration:underline', <SMALL> 65 </SMALL> 'snippets': [ <SMALL> 66 </SMALL> 'Brie is the queen of the cheeses<span style=color:red>!!!</span>' <SMALL> 67 </SMALL> ], <SMALL> 68 </SMALL> }, <SMALL> 69 </SMALL>} <SMALL> 70 </SMALL> <SMALL> 71 </SMALL> <SMALL> 72 </SMALL>def DefaultData(): <SMALL> 73 </SMALL> """Provides default data for Gruyere.""" <SMALL> 74 </SMALL> return copy.deepcopy(DEFAULT_DATA) </PRE></BODY></HTML> |
| URL | http://google-gruyere.appspot.com/code/gruyere.py |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 389 bytes. |
GET http://google-gruyere.appspot.com/code/gruyere.py HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/code/?gruyere.py Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 244 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: bb9a3355a53dd8a63ca7a9ba938eec94 Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 45208 |
| Response Body - size: 45,208 bytes. |
<HTML>
<STYLE> SMALL { color: #AAAAFF; font-size: 75% } BIG { color: #3333FF; font-size: 150%; font-weight: bold} </STYLE><BODY><PRE> <BIG>gruyere.py</BIG> <SMALL> 1 </SMALL>#!/usr/bin/env python2.7 <SMALL> 2 </SMALL> <SMALL> 3 </SMALL>"""Gruyere - a web application with holes. <SMALL> 4 </SMALL> <SMALL> 5 </SMALL>Copyright 2017 Google Inc. All rights reserved. <SMALL> 6 </SMALL> <SMALL> 7 </SMALL>This code is licensed under the <SMALL> 8 </SMALL>https://creativecommons.org/licenses/by-nd/3.0/us/ <SMALL> 9 </SMALL>Creative Commons Attribution-No Derivative Works 3.0 United States license. <SMALL> 10 </SMALL> <SMALL> 11 </SMALL>DO NOT COPY THIS CODE! <SMALL> 12 </SMALL> <SMALL> 13 </SMALL>This application is a small self-contained web application with numerous <SMALL> 14 </SMALL>security holes. It is provided for use with the Web Application Exploits and <SMALL> 15 </SMALL>Defenses codelab. You may modify the code for your own use while doing the <SMALL> 16 </SMALL>codelab but you may not distribute the modified code. Brief excerpts of this <SMALL> 17 </SMALL>code may be used for educational or instructional purposes provided this <SMALL> 18 </SMALL>notice is kept intact. By using Gruyere you agree to the Terms of Service <SMALL> 19 </SMALL>https://www.google.com/intl/en/policies/terms/ <SMALL> 20 </SMALL>""" <SMALL> 21 </SMALL> <SMALL> 22 </SMALL>__author__ = 'Bruce Leban' <SMALL> 23 </SMALL> <SMALL> 24 </SMALL># system modules <SMALL> 25 </SMALL>from BaseHTTPServer import BaseHTTPRequestHandler <SMALL> 26 </SMALL>from BaseHTTPServer import HTTPServer <SMALL> 27 </SMALL>import cgi <SMALL> 28 </SMALL>import cPickle <SMALL> 29 </SMALL>import os <SMALL> 30 </SMALL>import random <SMALL> 31 </SMALL>import sys <SMALL> 32 </SMALL>import threading <SMALL> 33 </SMALL>import urllib <SMALL> 34 </SMALL>from urlparse import urlparse <SMALL> 35 </SMALL> <SMALL> 36 </SMALL>try: <SMALL> 37 </SMALL> sys.dont_write_bytecode = True <SMALL> 38 </SMALL>except AttributeError: <SMALL> 39 </SMALL> pass <SMALL> 40 </SMALL> <SMALL> 41 </SMALL># our modules <SMALL> 42 </SMALL>import data <SMALL> 43 </SMALL>import gtl <SMALL> 44 </SMALL> <SMALL> 45 </SMALL> <SMALL> 46 </SMALL>DB_FILE = '/stored-data.txt' <SMALL> 47 </SMALL>SECRET_FILE = '/secret.txt' <SMALL> 48 </SMALL> <SMALL> 49 </SMALL>INSTALL_PATH = '.' <SMALL> 50 </SMALL>RESOURCE_PATH = 'resources' <SMALL> 51 </SMALL> <SMALL> 52 </SMALL>SPECIAL_COOKIE = '_cookie' <SMALL> 53 </SMALL>SPECIAL_PROFILE = '_profile' <SMALL> 54 </SMALL>SPECIAL_DB = '_db' <SMALL> 55 </SMALL>SPECIAL_PARAMS = '_params' <SMALL> 56 </SMALL>SPECIAL_UNIQUE_ID = '_unique_id' <SMALL> 57 </SMALL> <SMALL> 58 </SMALL>COOKIE_UID = 'uid' <SMALL> 59 </SMALL>COOKIE_ADMIN = 'is_admin' <SMALL> 60 </SMALL>COOKIE_AUTHOR = 'is_author' <SMALL> 61 </SMALL> <SMALL> 62 </SMALL> <SMALL> 63 </SMALL># Set to True to cause the server to exit after processing the current url. <SMALL> 64 </SMALL>quit_server = False <SMALL> 65 </SMALL> <SMALL> 66 </SMALL># A global copy of the database so that _GetDatabase can access it. <SMALL> 67 </SMALL>stored_data = None <SMALL> 68 </SMALL> <SMALL> 69 </SMALL># The HTTPServer object. <SMALL> 70 </SMALL>http_server = None <SMALL> 71 </SMALL> <SMALL> 72 </SMALL># A secret value used to generate hashes to protect cookies from tampering. <SMALL> 73 </SMALL>cookie_secret = '' <SMALL> 74 </SMALL> <SMALL> 75 </SMALL># File extensions of resource files that we recognize. <SMALL> 76 </SMALL>RESOURCE_CONTENT_TYPES = { <SMALL> 77 </SMALL> '.css': 'text/css', <SMALL> 78 </SMALL> '.gif': 'image/gif', <SMALL> 79 </SMALL> '.htm': 'text/html', <SMALL> 80 </SMALL> '.html': 'text/html', <SMALL> 81 </SMALL> '.js': 'application/javascript', <SMALL> 82 </SMALL> '.jpeg': 'image/jpeg', <SMALL> 83 </SMALL> '.jpg': 'image/jpeg', <SMALL> 84 </SMALL> '.png': 'image/png', <SMALL> 85 </SMALL> '.ico': 'image/x-icon', <SMALL> 86 </SMALL> '.text': 'text/plain', <SMALL> 87 </SMALL> '.txt': 'text/plain', <SMALL> 88 </SMALL>} <SMALL> 89 </SMALL> <SMALL> 90 </SMALL> <SMALL> 91 </SMALL>def main(): <SMALL> 92 </SMALL> _SetWorkingDirectory() <SMALL> 93 </SMALL> <SMALL> 94 </SMALL> global quit_server <SMALL> 95 </SMALL> quit_server = False <SMALL> 96 </SMALL> <SMALL> 97 </SMALL> # Normally, Gruyere only accepts connections to/from localhost. If you <SMALL> 98 </SMALL> # would like to allow access from other ip addresses, you can change to <SMALL> 99 </SMALL> # operate in a less secure mode. Set insecure_mode to True to serve on the <SMALL>100 </SMALL> # hostname instead of localhost and add the addresses of the other machines <SMALL>101 </SMALL> # to allowed_ips below. <SMALL>102 </SMALL> <SMALL>103 </SMALL> insecure_mode = False <SMALL>104 </SMALL> <SMALL>105 </SMALL> # WARNING! DO NOT CHANGE THE FOLLOWING SECTION OF CODE! <SMALL>106 </SMALL> <SMALL>107 </SMALL> # This application is very exploitable. It takes several precautions to <SMALL>108 </SMALL> # limit the risk from a real attacker: <SMALL>109 </SMALL> # (1) Serve requests on localhost so that it will not be accessible <SMALL>110 </SMALL> # from other machines. <SMALL>111 </SMALL> # (2) If a request is received from any IP other than localhost, quit. <SMALL>112 </SMALL> # (This protection is implemented in do_GET/do_POST.) <SMALL>113 </SMALL> # (3) Inject a random identifier as the first part of the path and <SMALL>114 </SMALL> # quit if a request is received without this identifier (except for an <SMALL>115 </SMALL> # empty path which redirects and /favicon.ico). <SMALL>116 </SMALL> # (4) Automatically exit after 2 hours (7200 seconds) to mitigate against <SMALL>117 </SMALL> # accidentally leaving the server running. <SMALL>118 </SMALL> <SMALL>119 </SMALL> quit_timer = threading.Timer(7200, lambda: _Exit('Timeout')) # DO NOT CHANGE <SMALL>120 </SMALL> quit_timer.start() # DO NOT CHANGE <SMALL>121 </SMALL> <SMALL>122 </SMALL> if insecure_mode: # DO NOT CHANGE <SMALL>123 </SMALL> server_name = os.popen('hostname').read().replace('\n', '') # DO NOT CHANGE <SMALL>124 </SMALL> else: # DO NOT CHANGE <SMALL>125 </SMALL> server_name = '127.0.0.1' # DO NOT CHANGE <SMALL>126 </SMALL> server_port = 8008 # DO NOT CHANGE <SMALL>127 </SMALL> <SMALL>128 </SMALL> # The unique id is created from a CSPRNG. <SMALL>129 </SMALL> try: # DO NOT CHANGE <SMALL>130 </SMALL> r = random.SystemRandom() # DO NOT CHANGE <SMALL>131 </SMALL> except NotImplementedError: # DO NOT CHANGE <SMALL>132 </SMALL> _Exit('Could not obtain a CSPRNG source') # DO NOT CHANGE <SMALL>133 </SMALL> <SMALL>134 </SMALL> global server_unique_id # DO NOT CHANGE <SMALL>135 </SMALL> server_unique_id = str(r.randint(2**128, 2**(128+1))) # DO NOT CHANGE <SMALL>136 </SMALL> <SMALL>137 </SMALL> # END WARNING! <SMALL>138 </SMALL> <SMALL>139 </SMALL> global http_server <SMALL>140 </SMALL> http_server = HTTPServer((server_name, server_port), <SMALL>141 </SMALL> GruyereRequestHandler) <SMALL>142 </SMALL> <SMALL>143 </SMALL> print >>sys.stderr, ''' <SMALL>144 </SMALL> Gruyere started... <SMALL>145 </SMALL> http://%s:%d/ <SMALL>146 </SMALL> http://%s:%d/%s/''' % ( <SMALL>147 </SMALL> server_name, server_port, server_name, server_port, <SMALL>148 </SMALL> server_unique_id) <SMALL>149 </SMALL> <SMALL>150 </SMALL> global stored_data <SMALL>151 </SMALL> stored_data = _LoadDatabase() <SMALL>152 </SMALL> <SMALL>153 </SMALL> while not quit_server: <SMALL>154 </SMALL> try: <SMALL>155 </SMALL> http_server.handle_request() <SMALL>156 </SMALL> _SaveDatabase(stored_data) <SMALL>157 </SMALL> except KeyboardInterrupt: <SMALL>158 </SMALL> print >>sys.stderr, '\nReceived KeyboardInterrupt' <SMALL>159 </SMALL> quit_server = True <SMALL>160 </SMALL> <SMALL>161 </SMALL> print >>sys.stderr, '\nClosing' <SMALL>162 </SMALL> http_server.socket.close() <SMALL>163 </SMALL> _Exit('quit_server') <SMALL>164 </SMALL> <SMALL>165 </SMALL> <SMALL>166 </SMALL>def _Exit(reason): <SMALL>167 </SMALL> # use os._exit instead of sys.exit because this can't be trapped <SMALL>168 </SMALL> print >>sys.stderr, '\nExit: ' + reason <SMALL>169 </SMALL> os._exit(0) <SMALL>170 </SMALL> <SMALL>171 </SMALL> <SMALL>172 </SMALL>def _SetWorkingDirectory(): <SMALL>173 </SMALL> """Set the working directory to the directory containing this file.""" <SMALL>174 </SMALL> if sys.path[0]: <SMALL>175 </SMALL> os.chdir(sys.path[0]) <SMALL>176 </SMALL> <SMALL>177 </SMALL> <SMALL>178 </SMALL>def _LoadDatabase(): <SMALL>179 </SMALL> """Load the database from stored-data.txt. <SMALL>180 </SMALL> <SMALL>181 </SMALL> Returns: <SMALL>182 </SMALL> The loaded database. <SMALL>183 </SMALL> """ <SMALL>184 </SMALL> <SMALL>185 </SMALL> try: <SMALL>186 </SMALL> f = _Open(INSTALL_PATH, DB_FILE) <SMALL>187 </SMALL> stored_data = cPickle.load(f) <SMALL>188 </SMALL> f.close() <SMALL>189 </SMALL> except (IOError, ValueError): <SMALL>190 </SMALL> _Log('Couldn\'t load data; expected the first time Gruyere is run') <SMALL>191 </SMALL> stored_data = None <SMALL>192 </SMALL> <SMALL>193 </SMALL> f = _Open(INSTALL_PATH, SECRET_FILE) <SMALL>194 </SMALL> global cookie_secret <SMALL>195 </SMALL> cookie_secret = f.readline() <SMALL>196 </SMALL> f.close() <SMALL>197 </SMALL> <SMALL>198 </SMALL> return stored_data <SMALL>199 </SMALL> <SMALL>200 </SMALL> <SMALL>201 </SMALL>def _SaveDatabase(save_database): <SMALL>202 </SMALL> """Save the database to stored-data.txt. <SMALL>203 </SMALL> <SMALL>204 </SMALL> Args: <SMALL>205 </SMALL> save_database: the database to save. <SMALL>206 </SMALL> """ <SMALL>207 </SMALL> <SMALL>208 </SMALL> try: <SMALL>209 </SMALL> f = _Open(INSTALL_PATH, DB_FILE, 'w') <SMALL>210 </SMALL> cPickle.dump(save_database, f) <SMALL>211 </SMALL> f.close() <SMALL>212 </SMALL> except IOError: <SMALL>213 </SMALL> _Log('Couldn\'t save data') <SMALL>214 </SMALL> <SMALL>215 </SMALL> <SMALL>216 </SMALL>def _Open(location, filename, mode='rb'): <SMALL>217 </SMALL> """Open a file from a specific location. <SMALL>218 </SMALL> <SMALL>219 </SMALL> Args: <SMALL>220 </SMALL> location: The directory containing the file. <SMALL>221 </SMALL> filename: The name of the file. <SMALL>222 </SMALL> mode: File mode for open(). <SMALL>223 </SMALL> <SMALL>224 </SMALL> Returns: <SMALL>225 </SMALL> A file object. <SMALL>226 </SMALL> """ <SMALL>227 </SMALL> return open(location + filename, mode) <SMALL>228 </SMALL> <SMALL>229 </SMALL> <SMALL>230 </SMALL>class GruyereRequestHandler(BaseHTTPRequestHandler): <SMALL>231 </SMALL> """Handle a http request.""" <SMALL>232 </SMALL> <SMALL>233 </SMALL> # An empty cookie <SMALL>234 </SMALL> NULL_COOKIE = {COOKIE_UID: None, COOKIE_ADMIN: False, COOKIE_AUTHOR: False} <SMALL>235 </SMALL> <SMALL>236 </SMALL> # Urls that can only be accessed by administrators. <SMALL>237 </SMALL> _PROTECTED_URLS = [ <SMALL>238 </SMALL> '/quit', <SMALL>239 </SMALL> '/reset' <SMALL>240 </SMALL> ] <SMALL>241 </SMALL> <SMALL>242 </SMALL> def _GetDatabase(self): <SMALL>243 </SMALL> """Gets the database.""" <SMALL>244 </SMALL> global stored_data <SMALL>245 </SMALL> if not stored_data: <SMALL>246 </SMALL> stored_data = data.DefaultData() <SMALL>247 </SMALL> return stored_data <SMALL>248 </SMALL> <SMALL>249 </SMALL> def _ResetDatabase(self): <SMALL>250 </SMALL> """Reset the database.""" <SMALL>251 </SMALL> # global stored_data <SMALL>252 </SMALL> stored_data = data.DefaultData() <SMALL>253 </SMALL> <SMALL>254 </SMALL> def _DoLogin(self, cookie, specials, params): <SMALL>255 </SMALL> """Handles the /login url: validates the user and creates a cookie. <SMALL>256 </SMALL> <SMALL>257 </SMALL> Args: <SMALL>258 </SMALL> cookie: The cookie for this request. <SMALL>259 </SMALL> specials: Other special values for this request. <SMALL>260 </SMALL> params: Cgi parameters. <SMALL>261 </SMALL> """ <SMALL>262 </SMALL> database = self._GetDatabase() <SMALL>263 </SMALL> message = '' <SMALL>264 </SMALL> if 'uid' in params and 'pw' in params: <SMALL>265 </SMALL> uid = self._GetParameter(params, 'uid') <SMALL>266 </SMALL> if uid in database: <SMALL>267 </SMALL> if database[uid]['pw'] == self._GetParameter(params, 'pw'): <SMALL>268 </SMALL> (cookie, new_cookie_text) = ( <SMALL>269 </SMALL> self._CreateCookie('GRUYERE', uid)) <SMALL>270 </SMALL> self._DoHome(cookie, specials, params, new_cookie_text) <SMALL>271 </SMALL> return <SMALL>272 </SMALL> message = 'Invalid user name or password.' <SMALL>273 </SMALL> # not logged in <SMALL>274 </SMALL> specials['_message'] = message <SMALL>275 </SMALL> self._SendTemplateResponse('/login.gtl', specials, params) <SMALL>276 </SMALL> <SMALL>277 </SMALL> def _DoLogout(self, cookie, specials, params): <SMALL>278 </SMALL> """Handles the /logout url: clears the cookie. <SMALL>279 </SMALL> <SMALL>280 </SMALL> Args: <SMALL>281 </SMALL> cookie: The cookie for this request. <SMALL>282 </SMALL> specials: Other special values for this request. <SMALL>283 </SMALL> params: Cgi parameters. <SMALL>284 </SMALL> """ <SMALL>285 </SMALL> (cookie, new_cookie_text) = ( <SMALL>286 </SMALL> self._CreateCookie('GRUYERE', None)) <SMALL>287 </SMALL> self._DoHome(cookie, specials, params, new_cookie_text) <SMALL>288 </SMALL> <SMALL>289 </SMALL> def _Do(self, cookie, specials, params): <SMALL>290 </SMALL> """Handles the home page (http://localhost/). <SMALL>291 </SMALL> <SMALL>292 </SMALL> Args: <SMALL>293 </SMALL> cookie: The cookie for this request. <SMALL>294 </SMALL> specials: Other special values for this request. <SMALL>295 </SMALL> params: Cgi parameters. <SMALL>296 </SMALL> """ <SMALL>297 </SMALL> self._DoHome(cookie, specials, params) <SMALL>298 </SMALL> <SMALL>299 </SMALL> def _DoHome(self, cookie, specials, params, new_cookie_text=None): <SMALL>300 </SMALL> """Renders the home page. <SMALL>301 </SMALL> <SMALL>302 </SMALL> Args: <SMALL>303 </SMALL> cookie: The cookie for this request. <SMALL>304 </SMALL> specials: Other special values for this request. <SMALL>305 </SMALL> params: Cgi parameters. <SMALL>306 </SMALL> new_cookie_text: New cookie. <SMALL>307 </SMALL> """ <SMALL>308 </SMALL> database = self._GetDatabase() <SMALL>309 </SMALL> specials[SPECIAL_COOKIE] = cookie <SMALL>310 </SMALL> if cookie and cookie.get(COOKIE_UID): <SMALL>311 </SMALL> specials[SPECIAL_PROFILE] = database.get(cookie[COOKIE_UID]) <SMALL>312 </SMALL> else: <SMALL>313 </SMALL> specials.pop(SPECIAL_PROFILE, None) <SMALL>314 </SMALL> self._SendTemplateResponse( <SMALL>315 </SMALL> '/home.gtl', specials, params, new_cookie_text) <SMALL>316 </SMALL> <SMALL>317 </SMALL> def _DoBadUrl(self, path, cookie, specials, params): <SMALL>318 </SMALL> """Handles invalid urls: displays an appropriate error message. <SMALL>319 </SMALL> <SMALL>320 </SMALL> Args: <SMALL>321 </SMALL> path: The invalid url. <SMALL>322 </SMALL> cookie: The cookie for this request. <SMALL>323 </SMALL> specials: Other special values for this request. <SMALL>324 </SMALL> params: Cgi parameters. <SMALL>325 </SMALL> """ <SMALL>326 </SMALL> self._SendError('Invalid request: %s' % (path,), cookie, specials, params) <SMALL>327 </SMALL> <SMALL>328 </SMALL> def _DoQuitserver(self, cookie, specials, params): <SMALL>329 </SMALL> """Handles the /quitserver url for administrators to quit the server. <SMALL>330 </SMALL> <SMALL>331 </SMALL> Args: <SMALL>332 </SMALL> cookie: The cookie for this request. (unused) <SMALL>333 </SMALL> specials: Other special values for this request. (unused) <SMALL>334 </SMALL> params: Cgi parameters. (unused) <SMALL>335 </SMALL> """ <SMALL>336 </SMALL> global quit_server <SMALL>337 </SMALL> quit_server = True <SMALL>338 </SMALL> self._SendTextResponse('Server quit.', None) <SMALL>339 </SMALL> <SMALL>340 </SMALL> def _AddParameter(self, name, params, data_dict, default=None): <SMALL>341 </SMALL> """Transfers a value (with a default) from the parameters to the data.""" <SMALL>342 </SMALL> if params.get(name): <SMALL>343 </SMALL> data_dict[name] = params[name][0] <SMALL>344 </SMALL> elif default is not None: <SMALL>345 </SMALL> data_dict[name] = default <SMALL>346 </SMALL> <SMALL>347 </SMALL> def _GetParameter(self, params, name, default=None): <SMALL>348 </SMALL> """Gets a parameter value with a default.""" <SMALL>349 </SMALL> if params.get(name): <SMALL>350 </SMALL> return params[name][0] <SMALL>351 </SMALL> return default <SMALL>352 </SMALL> <SMALL>353 </SMALL> def _GetSnippets(self, cookie, specials, create=False): <SMALL>354 </SMALL> """Returns all of the user's snippets.""" <SMALL>355 </SMALL> database = self._GetDatabase() <SMALL>356 </SMALL> try: <SMALL>357 </SMALL> profile = database[cookie[COOKIE_UID]] <SMALL>358 </SMALL> if create and 'snippets' not in profile: <SMALL>359 </SMALL> profile['snippets'] = [] <SMALL>360 </SMALL> snippets = profile['snippets'] <SMALL>361 </SMALL> except (KeyError, TypeError): <SMALL>362 </SMALL> _Log('Error getting snippets') <SMALL>363 </SMALL> return None <SMALL>364 </SMALL> return snippets <SMALL>365 </SMALL> <SMALL>366 </SMALL> def _DoNewsnippet2(self, cookie, specials, params): <SMALL>367 </SMALL> """Handles the /newsnippet2 url: actually add the snippet. <SMALL>368 </SMALL> <SMALL>369 </SMALL> Args: <SMALL>370 </SMALL> cookie: The cookie for this request. <SMALL>371 </SMALL> specials: Other special values for this request. <SMALL>372 </SMALL> params: Cgi parameters. <SMALL>373 </SMALL> """ <SMALL>374 </SMALL> snippet = self._GetParameter(params, 'snippet') <SMALL>375 </SMALL> if not snippet: <SMALL>376 </SMALL> self._SendError('No snippet!', cookie, specials, params) <SMALL>377 </SMALL> else: <SMALL>378 </SMALL> snippets = self._GetSnippets(cookie, specials, True) <SMALL>379 </SMALL> if snippets is not None: <SMALL>380 </SMALL> snippets.insert(0, snippet) <SMALL>381 </SMALL> self._SendRedirect('/snippets.gtl', specials[SPECIAL_UNIQUE_ID]) <SMALL>382 </SMALL> <SMALL>383 </SMALL> def _DoDeletesnippet(self, cookie, specials, params): <SMALL>384 </SMALL> """Handles the /deletesnippet url: delete the indexed snippet. <SMALL>385 </SMALL> <SMALL>386 </SMALL> Args: <SMALL>387 </SMALL> cookie: The cookie for this request. <SMALL>388 </SMALL> specials: Other special values for this request. <SMALL>389 </SMALL> params: Cgi parameters. <SMALL>390 </SMALL> """ <SMALL>391 </SMALL> index = self._GetParameter(params, 'index') <SMALL>392 </SMALL> snippets = self._GetSnippets(cookie, specials) <SMALL>393 </SMALL> try: <SMALL>394 </SMALL> del snippets[int(index)] <SMALL>395 </SMALL> except (IndexError, TypeError, ValueError): <SMALL>396 </SMALL> self._SendError( <SMALL>397 </SMALL> 'Invalid index (%s)' % (index,), <SMALL>398 </SMALL> cookie, specials, params) <SMALL>399 </SMALL> return <SMALL>400 </SMALL> self._SendRedirect('/snippets.gtl', specials[SPECIAL_UNIQUE_ID]) <SMALL>401 </SMALL> <SMALL>402 </SMALL> def _DoSaveprofile(self, cookie, specials, params): <SMALL>403 </SMALL> """Saves the user's profile. <SMALL>404 </SMALL> <SMALL>405 </SMALL> Args: <SMALL>406 </SMALL> cookie: The cookie for this request. <SMALL>407 </SMALL> specials: Other special values for this request. <SMALL>408 </SMALL> params: Cgi parameters. <SMALL>409 </SMALL> <SMALL>410 </SMALL> If the 'action' cgi parameter is 'new', then this is creating a new user <SMALL>411 </SMALL> and it's an error if the user already exists. If action is 'update', then <SMALL>412 </SMALL> this is editing an existing user's profile and it's an error if the user <SMALL>413 </SMALL> does not exist. <SMALL>414 </SMALL> """ <SMALL>415 </SMALL> <SMALL>416 </SMALL> # build new profile <SMALL>417 </SMALL> profile_data = {} <SMALL>418 </SMALL> uid = self._GetParameter(params, 'uid', cookie[COOKIE_UID]) <SMALL>419 </SMALL> newpw = self._GetParameter(params, 'pw') <SMALL>420 </SMALL> self._AddParameter('name', params, profile_data, uid) <SMALL>421 </SMALL> self._AddParameter('pw', params, profile_data) <SMALL>422 </SMALL> self._AddParameter('is_author', params, profile_data) <SMALL>423 </SMALL> self._AddParameter('is_admin', params, profile_data) <SMALL>424 </SMALL> self._AddParameter('private_snippet', params, profile_data) <SMALL>425 </SMALL> self._AddParameter('icon', params, profile_data) <SMALL>426 </SMALL> self._AddParameter('web_site', params, profile_data) <SMALL>427 </SMALL> self._AddParameter('color', params, profile_data) <SMALL>428 </SMALL> <SMALL>429 </SMALL> # Each case below has to set either error or redirect <SMALL>430 </SMALL> database = self._GetDatabase() <SMALL>431 </SMALL> message = None <SMALL>432 </SMALL> new_cookie_text = None <SMALL>433 </SMALL> action = self._GetParameter(params, 'action') <SMALL>434 </SMALL> if action == 'new': <SMALL>435 </SMALL> if uid in database: <SMALL>436 </SMALL> message = 'User already exists.' <SMALL>437 </SMALL> else: <SMALL>438 </SMALL> profile_data['pw'] = newpw <SMALL>439 </SMALL> database[uid] = profile_data <SMALL>440 </SMALL> (cookie, new_cookie_text) = self._CreateCookie('GRUYERE', uid) <SMALL>441 </SMALL> message = 'Account created.' # error message can also indicates success <SMALL>442 </SMALL> elif action == 'update': <SMALL>443 </SMALL> if uid not in database: <SMALL>444 </SMALL> message = 'User does not exist.' <SMALL>445 </SMALL> elif (newpw and database[uid]['pw'] != self._GetParameter(params, 'oldpw') <SMALL>446 </SMALL> and not cookie.get(COOKIE_ADMIN)): <SMALL>447 </SMALL> # must be admin or supply old pw to change password <SMALL>448 </SMALL> message = 'Incorrect password.' <SMALL>449 </SMALL> else: <SMALL>450 </SMALL> if newpw: <SMALL>451 </SMALL> profile_data['pw'] = newpw <SMALL>452 </SMALL> database[uid].update(profile_data) <SMALL>453 </SMALL> redirect = '/' <SMALL>454 </SMALL> else: <SMALL>455 </SMALL> message = 'Invalid request' <SMALL>456 </SMALL> _Log('SetProfile(%s, %s): %s' %(str(uid), str(action), str(message))) <SMALL>457 </SMALL> if message: <SMALL>458 </SMALL> self._SendError(message, cookie, specials, params, new_cookie_text) <SMALL>459 </SMALL> else: <SMALL>460 </SMALL> self._SendRedirect(redirect, specials[SPECIAL_UNIQUE_ID]) <SMALL>461 </SMALL> <SMALL>462 </SMALL> def _SendHtmlResponse(self, html, new_cookie_text=None): <SMALL>463 </SMALL> """Sends the provided html response with appropriate headers. <SMALL>464 </SMALL> <SMALL>465 </SMALL> Args: <SMALL>466 </SMALL> html: The response. <SMALL>467 </SMALL> new_cookie_text: New cookie to set. <SMALL>468 </SMALL> """ <SMALL>469 </SMALL> self.send_response(200) <SMALL>470 </SMALL> self.send_header('Content-type', 'text/html') <SMALL>471 </SMALL> self.send_header('Pragma', 'no-cache') <SMALL>472 </SMALL> if new_cookie_text: <SMALL>473 </SMALL> self.send_header('Set-Cookie', new_cookie_text) <SMALL>474 </SMALL> self.send_header('X-XSS-Protection', '0') <SMALL>475 </SMALL> self.end_headers() <SMALL>476 </SMALL> self.wfile.write(html) <SMALL>477 </SMALL> <SMALL>478 </SMALL> def _SendTextResponse(self, text, new_cookie_text=None): <SMALL>479 </SMALL> """Sends a verbatim text response.""" <SMALL>480 </SMALL> <SMALL>481 </SMALL> self._SendHtmlResponse('<pre>' + cgi.escape(text) + '</pre>', <SMALL>482 </SMALL> new_cookie_text) <SMALL>483 </SMALL> <SMALL>484 </SMALL> def _SendTemplateResponse(self, filename, specials, params, <SMALL>485 </SMALL> new_cookie_text=None): <SMALL>486 </SMALL> """Sends a response using a gtl template. <SMALL>487 </SMALL> <SMALL>488 </SMALL> Args: <SMALL>489 </SMALL> filename: The template file. <SMALL>490 </SMALL> specials: Other special values for this request. <SMALL>491 </SMALL> params: Cgi parameters. <SMALL>492 </SMALL> new_cookie_text: New cookie to set. <SMALL>493 </SMALL> """ <SMALL>494 </SMALL> f = None <SMALL>495 </SMALL> try: <SMALL>496 </SMALL> f = _Open(RESOURCE_PATH, filename) <SMALL>497 </SMALL> template = f.read() <SMALL>498 </SMALL> finally: <SMALL>499 </SMALL> if f: f.close() <SMALL>500 </SMALL> self._SendHtmlResponse( <SMALL>501 </SMALL> gtl.ExpandTemplate(template, specials, params), <SMALL>502 </SMALL> new_cookie_text) <SMALL>503 </SMALL> <SMALL>504 </SMALL> def _SendFileResponse(self, filename, cookie, specials, params): <SMALL>505 </SMALL> """Sends the contents of a file. <SMALL>506 </SMALL> <SMALL>507 </SMALL> Args: <SMALL>508 </SMALL> filename: The file to send. <SMALL>509 </SMALL> cookie: The cookie for this request. <SMALL>510 </SMALL> specials: Other special values for this request. <SMALL>511 </SMALL> params: Cgi parameters. <SMALL>512 </SMALL> """ <SMALL>513 </SMALL> content_type = None <SMALL>514 </SMALL> if filename.endswith('.gtl'): <SMALL>515 </SMALL> self._SendTemplateResponse(filename, specials, params) <SMALL>516 </SMALL> return <SMALL>517 </SMALL> <SMALL>518 </SMALL> name_only = filename[filename.rfind('/'):] <SMALL>519 </SMALL> extension = name_only[name_only.rfind('.'):] <SMALL>520 </SMALL> if '.' not in extension: <SMALL>521 </SMALL> content_type = 'text/plain' <SMALL>522 </SMALL> elif extension in RESOURCE_CONTENT_TYPES: <SMALL>523 </SMALL> content_type = RESOURCE_CONTENT_TYPES[extension] <SMALL>524 </SMALL> else: <SMALL>525 </SMALL> self._SendError( <SMALL>526 </SMALL> 'Unrecognized file type (%s).' % (filename,), <SMALL>527 </SMALL> cookie, specials, params) <SMALL>528 </SMALL> return <SMALL>529 </SMALL> f = None <SMALL>530 </SMALL> try: <SMALL>531 </SMALL> f = _Open(RESOURCE_PATH, filename, 'rb') <SMALL>532 </SMALL> self.send_response(200) <SMALL>533 </SMALL> self.send_header('Content-type', content_type) <SMALL>534 </SMALL> # Always cache static resources <SMALL>535 </SMALL> self.send_header('Cache-control', 'public, max-age=7200') <SMALL>536 </SMALL> self.send_header('X-XSS-Protection', '0') <SMALL>537 </SMALL> self.end_headers() <SMALL>538 </SMALL> self.wfile.write(f.read()) <SMALL>539 </SMALL> finally: <SMALL>540 </SMALL> if f: f.close() <SMALL>541 </SMALL> <SMALL>542 </SMALL> def _SendError(self, message, cookie, specials, params, new_cookie_text=None): <SMALL>543 </SMALL> """Sends an error message (using the error.gtl template). <SMALL>544 </SMALL> <SMALL>545 </SMALL> Args: <SMALL>546 </SMALL> message: The error to display. <SMALL>547 </SMALL> cookie: The cookie for this request. (unused) <SMALL>548 </SMALL> specials: Other special values for this request. <SMALL>549 </SMALL> params: Cgi parameters. <SMALL>550 </SMALL> new_cookie_text: New cookie to set. <SMALL>551 </SMALL> """ <SMALL>552 </SMALL> specials['_message'] = message <SMALL>553 </SMALL> self._SendTemplateResponse( <SMALL>554 </SMALL> '/error.gtl', specials, params, new_cookie_text) <SMALL>555 </SMALL> <SMALL>556 </SMALL> def _CreateCookie(self, cookie_name, uid): <SMALL>557 </SMALL> """Creates a cookie for this user. <SMALL>558 </SMALL> <SMALL>559 </SMALL> Args: <SMALL>560 </SMALL> cookie_name: Cookie to create. <SMALL>561 </SMALL> uid: The user. <SMALL>562 </SMALL> <SMALL>563 </SMALL> Returns: <SMALL>564 </SMALL> (cookie, new_cookie_text). <SMALL>565 </SMALL> <SMALL>566 </SMALL> The cookie contains all the information we need to know about <SMALL>567 </SMALL> the user for normal operations, including whether or not the user <SMALL>568 </SMALL> should have access to the authoring pages or the admin pages. <SMALL>569 </SMALL> The cookie is signed with a hash function. <SMALL>570 </SMALL> """ <SMALL>571 </SMALL> if uid is None: <SMALL>572 </SMALL> return (self.NULL_COOKIE, cookie_name + '=; path=/') <SMALL>573 </SMALL> database = self._GetDatabase() <SMALL>574 </SMALL> profile = database[uid] <SMALL>575 </SMALL> if profile.get('is_author', False): <SMALL>576 </SMALL> is_author = 'author' <SMALL>577 </SMALL> else: <SMALL>578 </SMALL> is_author = '' <SMALL>579 </SMALL> if profile.get('is_admin', False): <SMALL>580 </SMALL> is_admin = 'admin' <SMALL>581 </SMALL> else: <SMALL>582 </SMALL> is_admin = '' <SMALL>583 </SMALL> <SMALL>584 </SMALL> c = {COOKIE_UID: uid, COOKIE_ADMIN: is_admin, COOKIE_AUTHOR: is_author} <SMALL>585 </SMALL> c_data = '%s|%s|%s' % (uid, is_admin, is_author) <SMALL>586 </SMALL> <SMALL>587 </SMALL> # global cookie_secret; only use positive hash values <SMALL>588 </SMALL> h_data = str(hash(cookie_secret + c_data) & 0x7FFFFFF) <SMALL>589 </SMALL> c_text = '%s=%s|%s; path=/' % (cookie_name, h_data, c_data) <SMALL>590 </SMALL> return (c, c_text) <SMALL>591 </SMALL> <SMALL>592 </SMALL> def _GetCookie(self, cookie_name): <SMALL>593 </SMALL> """Reads, verifies and parses the cookie. <SMALL>594 </SMALL> <SMALL>595 </SMALL> Args: <SMALL>596 </SMALL> cookie_name: The cookie to get. <SMALL>597 </SMALL> <SMALL>598 </SMALL> Returns: <SMALL>599 </SMALL> a dict containing user, is_admin, and is_author if the cookie <SMALL>600 </SMALL> is present and valid. Otherwise, None. <SMALL>601 </SMALL> """ <SMALL>602 </SMALL> cookies = self.headers.get('Cookie') <SMALL>603 </SMALL> if isinstance(cookies, str): <SMALL>604 </SMALL> for c in cookies.split(';'): <SMALL>605 </SMALL> matched_cookie = self._MatchCookie(cookie_name, c) <SMALL>606 </SMALL> if matched_cookie: <SMALL>607 </SMALL> return self._ParseCookie(matched_cookie) <SMALL>608 </SMALL> return self.NULL_COOKIE <SMALL>609 </SMALL> <SMALL>610 </SMALL> def _MatchCookie(self, cookie_name, cookie): <SMALL>611 </SMALL> """Matches the cookie. <SMALL>612 </SMALL> <SMALL>613 </SMALL> Args: <SMALL>614 </SMALL> cookie_name: The name of the cookie. <SMALL>615 </SMALL> cookie: The full cookie (name=value). <SMALL>616 </SMALL> <SMALL>617 </SMALL> Returns: <SMALL>618 </SMALL> The cookie if it matches or None if it doesn't match. <SMALL>619 </SMALL> """ <SMALL>620 </SMALL> try: <SMALL>621 </SMALL> (cn, cd) = cookie.strip().split('=', 1) <SMALL>622 </SMALL> if cn != cookie_name: <SMALL>623 </SMALL> return None <SMALL>624 </SMALL> except (IndexError, ValueError): <SMALL>625 </SMALL> return None <SMALL>626 </SMALL> return cd <SMALL>627 </SMALL> <SMALL>628 </SMALL> def _ParseCookie(self, cookie): <SMALL>629 </SMALL> """Parses the cookie and returns NULL_COOKIE if it's invalid. <SMALL>630 </SMALL> <SMALL>631 </SMALL> Args: <SMALL>632 </SMALL> cookie: The text of the cookie. <SMALL>633 </SMALL> <SMALL>634 </SMALL> Returns: <SMALL>635 </SMALL> A map containing the values in the cookie. <SMALL>636 </SMALL> """ <SMALL>637 </SMALL> try: <SMALL>638 </SMALL> (hashed, cookie_data) = cookie.split('|', 1) <SMALL>639 </SMALL> # global cookie_secret <SMALL>640 </SMALL> if hashed != str(hash(cookie_secret + cookie_data) & 0x7FFFFFF): <SMALL>641 </SMALL> return self.NULL_COOKIE <SMALL>642 </SMALL> values = cookie_data.split('|') <SMALL>643 </SMALL> return { <SMALL>644 </SMALL> COOKIE_UID: values[0], <SMALL>645 </SMALL> COOKIE_ADMIN: values[1] == 'admin', <SMALL>646 </SMALL> COOKIE_AUTHOR: values[2] == 'author', <SMALL>647 </SMALL> } <SMALL>648 </SMALL> except (IndexError, ValueError): <SMALL>649 </SMALL> return self.NULL_COOKIE <SMALL>650 </SMALL> <SMALL>651 </SMALL> def _DoReset(self, cookie, specials, params): # debug only; resets this db <SMALL>652 </SMALL> """Handles the /reset url for administrators to reset the database. <SMALL>653 </SMALL> <SMALL>654 </SMALL> Args: <SMALL>655 </SMALL> cookie: The cookie for this request. (unused) <SMALL>656 </SMALL> specials: Other special values for this request. (unused) <SMALL>657 </SMALL> params: Cgi parameters. (unused) <SMALL>658 </SMALL> """ <SMALL>659 </SMALL> self._ResetDatabase() <SMALL>660 </SMALL> self._SendTextResponse('Server reset to default values...', None) <SMALL>661 </SMALL> <SMALL>662 </SMALL> def _DoUpload2(self, cookie, specials, params): <SMALL>663 </SMALL> """Handles the /upload2 url: finish the upload and save the file. <SMALL>664 </SMALL> <SMALL>665 </SMALL> Args: <SMALL>666 </SMALL> cookie: The cookie for this request. <SMALL>667 </SMALL> specials: Other special values for this request. <SMALL>668 </SMALL> params: Cgi parameters. (unused) <SMALL>669 </SMALL> """ <SMALL>670 </SMALL> (filename, file_data) = self._ExtractFileFromRequest() <SMALL>671 </SMALL> directory = self._MakeUserDirectory(cookie[COOKIE_UID]) <SMALL>672 </SMALL> <SMALL>673 </SMALL> message = None <SMALL>674 </SMALL> url = None <SMALL>675 </SMALL> try: <SMALL>676 </SMALL> f = _Open(directory, filename, 'wb') <SMALL>677 </SMALL> f.write(file_data) <SMALL>678 </SMALL> f.close() <SMALL>679 </SMALL> (host, port) = http_server.server_address <SMALL>680 </SMALL> url = 'http://%s:%d/%s/%s/%s' % ( <SMALL>681 </SMALL> host, port, specials[SPECIAL_UNIQUE_ID], cookie[COOKIE_UID], filename) <SMALL>682 </SMALL> except IOError, ex: <SMALL>683 </SMALL> message = 'Couldn\'t write file %s: %s' % (filename, ex.message) <SMALL>684 </SMALL> _Log(message) <SMALL>685 </SMALL> <SMALL>686 </SMALL> specials['_message'] = message <SMALL>687 </SMALL> self._SendTemplateResponse( <SMALL>688 </SMALL> '/upload2.gtl', specials, <SMALL>689 </SMALL> {'url': url}) <SMALL>690 </SMALL> <SMALL>691 </SMALL> def _ExtractFileFromRequest(self): <SMALL>692 </SMALL> """Extracts the file from an upload request. <SMALL>693 </SMALL> <SMALL>694 </SMALL> Returns: <SMALL>695 </SMALL> (filename, file_data) <SMALL>696 </SMALL> """ <SMALL>697 </SMALL> form = cgi.FieldStorage( <SMALL>698 </SMALL> fp=self.rfile, <SMALL>699 </SMALL> headers=self.headers, <SMALL>700 </SMALL> environ={'REQUEST_METHOD': 'POST', <SMALL>701 </SMALL> 'CONTENT_TYPE': self.headers.getheader('content-type')}) <SMALL>702 </SMALL> <SMALL>703 </SMALL> upload_file = form['upload_file'] <SMALL>704 </SMALL> file_data = upload_file.file.read() <SMALL>705 </SMALL> return (upload_file.filename, file_data) <SMALL>706 </SMALL> <SMALL>707 </SMALL> def _MakeUserDirectory(self, uid): <SMALL>708 </SMALL> """Creates a separate directory for each user to avoid upload conflicts. <SMALL>709 </SMALL> <SMALL>710 </SMALL> Args: <SMALL>711 </SMALL> uid: The user to create a directory for. <SMALL>712 </SMALL> <SMALL>713 </SMALL> Returns: <SMALL>714 </SMALL> The new directory path (/uid/). <SMALL>715 </SMALL> """ <SMALL>716 </SMALL> <SMALL>717 </SMALL> directory = RESOURCE_PATH + os.sep + str(uid) + os.sep <SMALL>718 </SMALL> try: <SMALL>719 </SMALL> print 'mkdir: ', directory <SMALL>720 </SMALL> os.mkdir(directory) <SMALL>721 </SMALL> # throws an exception if directory already exists, <SMALL>722 </SMALL> # however exception type varies by platform <SMALL>723 </SMALL> except Exception: <SMALL>724 </SMALL> pass # just ignore it if it already exists <SMALL>725 </SMALL> return directory <SMALL>726 </SMALL> <SMALL>727 </SMALL> def _SendRedirect(self, url, unique_id): <SMALL>728 </SMALL> """Sends a 302 redirect. <SMALL>729 </SMALL> <SMALL>730 </SMALL> Automatically adds the unique_id. <SMALL>731 </SMALL> <SMALL>732 </SMALL> Args: <SMALL>733 </SMALL> url: The location to redirect to which must start with '/'. <SMALL>734 </SMALL> unique_id: The unique id to include in the url. <SMALL>735 </SMALL> """ <SMALL>736 </SMALL> if not url: <SMALL>737 </SMALL> url = '/' <SMALL>738 </SMALL> url = '/' + unique_id + url <SMALL>739 </SMALL> self.send_response(302) <SMALL>740 </SMALL> self.send_header('Location', url) <SMALL>741 </SMALL> self.send_header('Pragma', 'no-cache') <SMALL>742 </SMALL> self.send_header('Content-type', 'text/html') <SMALL>743 </SMALL> self.send_header('X-XSS-Protection', '0') <SMALL>744 </SMALL> self.end_headers() <SMALL>745 </SMALL> self.wfile.write( <SMALL>746 </SMALL> '''<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML//EN'> <SMALL>747 </SMALL> <html><body> <SMALL>748 </SMALL> <title>302 Redirect</title> <SMALL>749 </SMALL> Redirected <a href="%s">here</a> <SMALL>750 </SMALL> </body></html>''' <SMALL>751 </SMALL> % (url,)) <SMALL>752 </SMALL> <SMALL>753 </SMALL> def _GetHandlerFunction(self, path): <SMALL>754 </SMALL> try: <SMALL>755 </SMALL> return getattr(GruyereRequestHandler, '_Do' + path[1:].capitalize()) <SMALL>756 </SMALL> except AttributeError: <SMALL>757 </SMALL> return None <SMALL>758 </SMALL> <SMALL>759 </SMALL> def do_POST(self): # part of BaseHTTPRequestHandler interface <SMALL>760 </SMALL> self.DoGetOrPost() <SMALL>761 </SMALL> <SMALL>762 </SMALL> def do_GET(self): # part of BaseHTTPRequestHandler interface <SMALL>763 </SMALL> self.DoGetOrPost() <SMALL>764 </SMALL> <SMALL>765 </SMALL> def DoGetOrPost(self): <SMALL>766 </SMALL> """Validate an http get or post request and call HandleRequest.""" <SMALL>767 </SMALL> <SMALL>768 </SMALL> url = urlparse(self.path) <SMALL>769 </SMALL> path = url[2] <SMALL>770 </SMALL> query = url[4] <SMALL>771 </SMALL> <SMALL>772 </SMALL> # Normally, Gruyere only accepts connections to/from localhost. If you <SMALL>773 </SMALL> # would like to allow access from other ip addresses, add the addresses <SMALL>774 </SMALL> # of the other machines to allowed_ips and change insecure_mode to True <SMALL>775 </SMALL> # above. This makes the application more vulnerable to a real attack so <SMALL>776 </SMALL> # you should only add ips of machines you completely control and make <SMALL>777 </SMALL> # sure that you are not using them to access any other web pages while <SMALL>778 </SMALL> # you are using Gruyere. <SMALL>779 </SMALL> <SMALL>780 </SMALL> allowed_ips = ['127.0.0.1'] <SMALL>781 </SMALL> <SMALL>782 </SMALL> # WARNING! DO NOT CHANGE THE FOLLOWING SECTION OF CODE! <SMALL>783 </SMALL> <SMALL>784 </SMALL> # This application is very exploitable. See main for details. What we're <SMALL>785 </SMALL> # doing here is (2) and (3) on the previous list: <SMALL>786 </SMALL> # (2) If a request is received from any IP other than localhost, quit. <SMALL>787 </SMALL> # An external attacker could still mount an attack on this IP by putting <SMALL>788 </SMALL> # an attack on an external web page, e.g., a web page that redirects to <SMALL>789 </SMALL> # a vulnerable url on 127.0.0.1 (which is why we use a random number). <SMALL>790 </SMALL> # (3) Inject a random identifier as the first part of the path and <SMALL>791 </SMALL> # quit if a request is received without this identifier (except for an <SMALL>792 </SMALL> # empty path which redirects and /favicon.ico). <SMALL>793 </SMALL> <SMALL>794 </SMALL> request_ip = self.client_address[0] # DO NOT CHANGE <SMALL>795 </SMALL> if request_ip not in allowed_ips: # DO NOT CHANGE <SMALL>796 </SMALL> print >>sys.stderr, ( # DO NOT CHANGE <SMALL>797 </SMALL> 'DANGER! Request from bad ip: ' + request_ip) # DO NOT CHANGE <SMALL>798 </SMALL> _Exit('bad_ip') # DO NOT CHANGE <SMALL>799 </SMALL> <SMALL>800 </SMALL> if (server_unique_id not in path # DO NOT CHANGE <SMALL>801 </SMALL> and path != '/favicon.ico'): # DO NOT CHANGE <SMALL>802 </SMALL> if path == '' or path == '/': # DO NOT CHANGE <SMALL>803 </SMALL> self._SendRedirect('/', server_unique_id) # DO NOT CHANGE <SMALL>804 </SMALL> return # DO NOT CHANGE <SMALL>805 </SMALL> else: # DO NOT CHANGE <SMALL>806 </SMALL> print >>sys.stderr, ( # DO NOT CHANGE <SMALL>807 </SMALL> 'DANGER! Request without unique id: ' + path) # DO NOT CHANGE <SMALL>808 </SMALL> _Exit('bad_id') # DO NOT CHANGE <SMALL>809 </SMALL> <SMALL>810 </SMALL> path = path.replace('/' + server_unique_id, '', 1) # DO NOT CHANGE <SMALL>811 </SMALL> <SMALL>812 </SMALL> # END WARNING! <SMALL>813 </SMALL> <SMALL>814 </SMALL> self.HandleRequest(path, query, server_unique_id) <SMALL>815 </SMALL> <SMALL>816 </SMALL> def HandleRequest(self, path, query, unique_id): <SMALL>817 </SMALL> """Handles an http request. <SMALL>818 </SMALL> <SMALL>819 </SMALL> Args: <SMALL>820 </SMALL> path: The path part of the url, with leading slash. <SMALL>821 </SMALL> query: The query part of the url, without leading question mark. <SMALL>822 </SMALL> unique_id: The unique id from the url. <SMALL>823 </SMALL> """ <SMALL>824 </SMALL> <SMALL>825 </SMALL> path = urllib.unquote(path) <SMALL>826 </SMALL> <SMALL>827 </SMALL> if not path: <SMALL>828 </SMALL> self._SendRedirect('/', server_unique_id) <SMALL>829 </SMALL> return <SMALL>830 </SMALL> params = cgi.parse_qs(query) # url.query <SMALL>831 </SMALL> specials = {} <SMALL>832 </SMALL> cookie = self._GetCookie('GRUYERE') <SMALL>833 </SMALL> database = self._GetDatabase() <SMALL>834 </SMALL> specials[SPECIAL_COOKIE] = cookie <SMALL>835 </SMALL> specials[SPECIAL_DB] = database <SMALL>836 </SMALL> specials[SPECIAL_PROFILE] = database.get(cookie.get(COOKIE_UID)) <SMALL>837 </SMALL> specials[SPECIAL_PARAMS] = params <SMALL>838 </SMALL> specials[SPECIAL_UNIQUE_ID] = unique_id <SMALL>839 </SMALL> <SMALL>840 </SMALL> if path in self._PROTECTED_URLS and not cookie[COOKIE_ADMIN]: <SMALL>841 </SMALL> self._SendError('Invalid request', cookie, specials, params) <SMALL>842 </SMALL> return <SMALL>843 </SMALL> <SMALL>844 </SMALL> try: <SMALL>845 </SMALL> handler = self._GetHandlerFunction(path) <SMALL>846 </SMALL> if callable(handler): <SMALL>847 </SMALL> (handler)(self, cookie, specials, params) <SMALL>848 </SMALL> else: <SMALL>849 </SMALL> try: <SMALL>850 </SMALL> self._SendFileResponse(path, cookie, specials, params) <SMALL>851 </SMALL> except IOError: <SMALL>852 </SMALL> self._DoBadUrl(path, cookie, specials, params) <SMALL>853 </SMALL> except KeyboardInterrupt: <SMALL>854 </SMALL> _Exit('KeyboardInterrupt') <SMALL>855 </SMALL> <SMALL>856 </SMALL> <SMALL>857 </SMALL>def _Log(message): <SMALL>858 </SMALL> print >>sys.stderr, message <SMALL>859 </SMALL> <SMALL>860 </SMALL> <SMALL>861 </SMALL>if __name__ == '__main__': <SMALL>862 </SMALL> main() </PRE></BODY></HTML> |
| URL | http://google-gruyere.appspot.com/code/gtl.py |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 381 bytes. |
GET http://google-gruyere.appspot.com/code/gtl.py HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/code/?gtl.py Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 244 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: 7c3910c9ad359e9786c9ea96d012a53d Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 15231 |
| Response Body - size: 15,231 bytes. |
<HTML>
<STYLE> SMALL { color: #AAAAFF; font-size: 75% } BIG { color: #3333FF; font-size: 150%; font-weight: bold} </STYLE><BODY><PRE> <BIG>gtl.py</BIG> <SMALL> 1 </SMALL>"""Gruyere Template Language, part of Gruyere, a web application with holes. <SMALL> 2 </SMALL> <SMALL> 3 </SMALL>Copyright 2017 Google Inc. All rights reserved. <SMALL> 4 </SMALL> <SMALL> 5 </SMALL>This code is licensed under the https://creativecommons.org/licenses/by-nd/3.0/us/ <SMALL> 6 </SMALL>Creative Commons Attribution-No Derivative Works 3.0 United States license. <SMALL> 7 </SMALL> <SMALL> 8 </SMALL>DO NOT COPY THIS CODE! <SMALL> 9 </SMALL> <SMALL> 10 </SMALL>This application is a small self-contained web application with numerous <SMALL> 11 </SMALL>security holes. It is provided for use with the Web Application Exploits and <SMALL> 12 </SMALL>Defenses codelab. You may modify the code for your own use while doing the <SMALL> 13 </SMALL>codelab but you may not distribute the modified code. Brief excerpts of this <SMALL> 14 </SMALL>code may be used for educational or instructional purposes provided this <SMALL> 15 </SMALL>notice is kept intact. By using Gruyere you agree to the Terms of Service <SMALL> 16 </SMALL>https://www.google.com/intl/en/policies/terms/ <SMALL> 17 </SMALL>""" <SMALL> 18 </SMALL> <SMALL> 19 </SMALL>__author__ = 'Bruce Leban' <SMALL> 20 </SMALL> <SMALL> 21 </SMALL># system modules <SMALL> 22 </SMALL>import cgi <SMALL> 23 </SMALL>import logging <SMALL> 24 </SMALL>import operator <SMALL> 25 </SMALL>import os <SMALL> 26 </SMALL>import pprint <SMALL> 27 </SMALL>import sys <SMALL> 28 </SMALL> <SMALL> 29 </SMALL># our modules <SMALL> 30 </SMALL>import gruyere <SMALL> 31 </SMALL>import sanitize <SMALL> 32 </SMALL> <SMALL> 33 </SMALL> <SMALL> 34 </SMALL>def ExpandTemplate(template, specials, params, name=''): <SMALL> 35 </SMALL> """Expands a template. <SMALL> 36 </SMALL> <SMALL> 37 </SMALL> Args: <SMALL> 38 </SMALL> template: a string template. <SMALL> 39 </SMALL> specials: a dict of special values. <SMALL> 40 </SMALL> params: a dict of parameter values. <SMALL> 41 </SMALL> name: the name of the _this object. <SMALL> 42 </SMALL> <SMALL> 43 </SMALL> Returns: <SMALL> 44 </SMALL> the expanded template. <SMALL> 45 </SMALL> <SMALL> 46 </SMALL> The template language includes these block structures: <SMALL> 47 </SMALL> <SMALL> 48 </SMALL> [[include:<filename>]] ...[[/include:<filename>]] <SMALL> 49 </SMALL> Insert the file or if the file cannot be opened insert the contents of <SMALL> 50 </SMALL> the block. The path should use / as a separator regardless of what <SMALL> 51 </SMALL> the underlying operating system is. <SMALL> 52 </SMALL> <SMALL> 53 </SMALL> [[for:<variable>]] ... [[/for:<variable>]] <SMALL> 54 </SMALL> Iterate over the variable (which should be a mapping or sequence) and <SMALL> 55 </SMALL> insert the block once for each value. Inside the loop _key is bound to <SMALL> 56 </SMALL> the key value for the iteration. <SMALL> 57 </SMALL> <SMALL> 58 </SMALL> [[if:<variable>]] ... [[/if:<variable>]] <SMALL> 59 </SMALL> Expand the contents of the block if the variable is not 'false'. There <SMALL> 60 </SMALL> is no else; use [[if:!<variable>]] instead. <SMALL> 61 </SMALL> <SMALL> 62 </SMALL> Note that in each case the end tags must match the begin tags with a <SMALL> 63 </SMALL> leading slash. This prevents mismatched tags and makes it easier to parse. <SMALL> 64 </SMALL> <SMALL> 65 </SMALL> The variable syntax is: <SMALL> 66 </SMALL> <SMALL> 67 </SMALL> {{<field>[.<field>]*[:<escaper>]}} <SMALL> 68 </SMALL> <SMALL> 69 </SMALL> where <field> is: <SMALL> 70 </SMALL> <SMALL> 71 </SMALL> a key to extract from a mapping <SMALL> 72 </SMALL> a number to extract from a sequence <SMALL> 73 </SMALL> <SMALL> 74 </SMALL> Variable names that start with '_' are special values: <SMALL> 75 </SMALL> _key = iteration key (inside loops) <SMALL> 76 </SMALL> _this = iteration value (inside loop) <SMALL> 77 </SMALL> _db = the database <SMALL> 78 </SMALL> _cookie = the user's cookie <SMALL> 79 </SMALL> _profile = the user's profile ~ _db.*(_cookie.user) <SMALL> 80 </SMALL> <SMALL> 81 </SMALL> If a field name starts with '*' it refers to a dereferenced parameter (orx <SMALL> 82 </SMALL> *_this). For example, _db.*uid retrieves the entry from _db matching the <SMALL> 83 </SMALL> uid parameter. <SMALL> 84 </SMALL> <SMALL> 85 </SMALL> The comment syntax is: <SMALL> 86 </SMALL> <SMALL> 87 </SMALL> {{#<comment>}} <SMALL> 88 </SMALL> """ <SMALL> 89 </SMALL> t = _ExpandBlocks(template, specials, params, name) <SMALL> 90 </SMALL> t = _ExpandVariables(t, specials, params, name) <SMALL> 91 </SMALL> return t <SMALL> 92 </SMALL> <SMALL> 93 </SMALL> <SMALL> 94 </SMALL>BLOCK_OPEN = '[[' <SMALL> 95 </SMALL>END_BLOCK_OPEN = '[[/' <SMALL> 96 </SMALL>BLOCK_CLOSE = ']]' <SMALL> 97 </SMALL> <SMALL> 98 </SMALL> <SMALL> 99 </SMALL>def _ExpandBlocks(template, specials, params, name): <SMALL>100 </SMALL> """Expands all the blocks in a template.""" <SMALL>101 </SMALL> result = [] <SMALL>102 </SMALL> rest = template <SMALL>103 </SMALL> while rest: <SMALL>104 </SMALL> tag, before_tag, after_tag = _FindTag(rest, BLOCK_OPEN, BLOCK_CLOSE) <SMALL>105 </SMALL> if tag is None: <SMALL>106 </SMALL> break <SMALL>107 </SMALL> end_tag = END_BLOCK_OPEN + tag + BLOCK_CLOSE <SMALL>108 </SMALL> before_end = rest.find(end_tag, after_tag) <SMALL>109 </SMALL> if before_end < 0: <SMALL>110 </SMALL> break <SMALL>111 </SMALL> after_end = before_end + len(end_tag) <SMALL>112 </SMALL> <SMALL>113 </SMALL> result.append(rest[:before_tag]) <SMALL>114 </SMALL> block = rest[after_tag:before_end] <SMALL>115 </SMALL> result.append(_ExpandBlock(tag, block, specials, params, name)) <SMALL>116 </SMALL> rest = rest[after_end:] <SMALL>117 </SMALL> return ''.join(result) + rest <SMALL>118 </SMALL> <SMALL>119 </SMALL> <SMALL>120 </SMALL>VAR_OPEN = '{{' <SMALL>121 </SMALL>VAR_CLOSE = '}}' <SMALL>122 </SMALL> <SMALL>123 </SMALL> <SMALL>124 </SMALL>def _ExpandVariables(template, specials, params, name): <SMALL>125 </SMALL> """Expands all the variables in a template.""" <SMALL>126 </SMALL> result = [] <SMALL>127 </SMALL> rest = template <SMALL>128 </SMALL> while rest: <SMALL>129 </SMALL> tag, before_tag, after_tag = _FindTag(rest, VAR_OPEN, VAR_CLOSE) <SMALL>130 </SMALL> if tag is None: <SMALL>131 </SMALL> break <SMALL>132 </SMALL> result.append(rest[:before_tag]) <SMALL>133 </SMALL> result.append(str(_ExpandVariable(tag, specials, params, name))) <SMALL>134 </SMALL> rest = rest[after_tag:] <SMALL>135 </SMALL> return ''.join(result) + rest <SMALL>136 </SMALL> <SMALL>137 </SMALL> <SMALL>138 </SMALL>FOR_TAG = 'for' <SMALL>139 </SMALL>IF_TAG = 'if' <SMALL>140 </SMALL>INCLUDE_TAG = 'include' <SMALL>141 </SMALL> <SMALL>142 </SMALL> <SMALL>143 </SMALL>def _ExpandBlock(tag, template, specials, params, name): <SMALL>144 </SMALL> """Expands a single template block.""" <SMALL>145 </SMALL> <SMALL>146 </SMALL> tag_type, block_var = tag.split(':', 1) <SMALL>147 </SMALL> if tag_type == INCLUDE_TAG: <SMALL>148 </SMALL> return _ExpandInclude(tag, block_var, template, specials, params, name) <SMALL>149 </SMALL> elif tag_type == IF_TAG: <SMALL>150 </SMALL> block_data = _ExpandVariable(block_var, specials, params, name) <SMALL>151 </SMALL> if block_data: <SMALL>152 </SMALL> return ExpandTemplate(template, specials, params, name) <SMALL>153 </SMALL> return '' <SMALL>154 </SMALL> elif tag_type == FOR_TAG: <SMALL>155 </SMALL> block_data = _ExpandVariable(block_var, specials, params, name) <SMALL>156 </SMALL> return _ExpandFor(tag, template, specials, block_data) <SMALL>157 </SMALL> else: <SMALL>158 </SMALL> _Log('Error: Invalid block: %s' % (tag,)) <SMALL>159 </SMALL> return '' <SMALL>160 </SMALL> <SMALL>161 </SMALL> <SMALL>162 </SMALL>def _ExpandInclude(_, filename, template, specials, params, name): <SMALL>163 </SMALL> """Expands an include block (or insert the template on an error).""" <SMALL>164 </SMALL> result = '' <SMALL>165 </SMALL> # replace /s with local file system equivalent <SMALL>166 </SMALL> fname = os.sep + filename.replace('/', os.sep) <SMALL>167 </SMALL> f = None <SMALL>168 </SMALL> try: <SMALL>169 </SMALL> try: <SMALL>170 </SMALL> f = gruyere._Open(gruyere.RESOURCE_PATH, fname) <SMALL>171 </SMALL> result = f.read() <SMALL>172 </SMALL> except IOError: <SMALL>173 </SMALL> _Log('Error: missing filename: %s' % (filename,)) <SMALL>174 </SMALL> result = template <SMALL>175 </SMALL> finally: <SMALL>176 </SMALL> if f: f.close() <SMALL>177 </SMALL> return ExpandTemplate(result, specials, params, name) <SMALL>178 </SMALL> <SMALL>179 </SMALL> <SMALL>180 </SMALL>def _ExpandFor(tag, template, specials, block_data): <SMALL>181 </SMALL> """Expands a for block iterating over the block_data.""" <SMALL>182 </SMALL> result = [] <SMALL>183 </SMALL> if operator.isMappingType(block_data): <SMALL>184 </SMALL> for v in block_data: <SMALL>185 </SMALL> result.append(ExpandTemplate(template, specials, block_data[v], v)) <SMALL>186 </SMALL> elif operator.isSequenceType(block_data): <SMALL>187 </SMALL> for i in xrange(len(block_data)): <SMALL>188 </SMALL> result.append(ExpandTemplate(template, specials, block_data[i], str(i))) <SMALL>189 </SMALL> else: <SMALL>190 </SMALL> _Log('Error: Invalid type: %s' % (tag,)) <SMALL>191 </SMALL> return '' <SMALL>192 </SMALL> return ''.join(result) <SMALL>193 </SMALL> <SMALL>194 </SMALL> <SMALL>195 </SMALL>def _ExpandVariable(var, specials, params, name, default=''): <SMALL>196 </SMALL> """Gets a variable value.""" <SMALL>197 </SMALL> if var.startswith('#'): # this is a comment. <SMALL>198 </SMALL> return '' <SMALL>199 </SMALL> <SMALL>200 </SMALL> # Strip out leading ! which negates value <SMALL>201 </SMALL> inverted = var.startswith('!') <SMALL>202 </SMALL> if inverted: <SMALL>203 </SMALL> var = var[1:] <SMALL>204 </SMALL> <SMALL>205 </SMALL> # Strip out trailing :<escaper> <SMALL>206 </SMALL> escaper_name = None <SMALL>207 </SMALL> if var.find(':') >= 0: <SMALL>208 </SMALL> (var, escaper_name) = var.split(':', 1) <SMALL>209 </SMALL> <SMALL>210 </SMALL> value = _ExpandValue(var, specials, params, name, default) <SMALL>211 </SMALL> if inverted: <SMALL>212 </SMALL> value = not value <SMALL>213 </SMALL> <SMALL>214 </SMALL> if escaper_name == 'text': <SMALL>215 </SMALL> value = cgi.escape(str(value)) <SMALL>216 </SMALL> elif escaper_name == 'html': <SMALL>217 </SMALL> value = sanitize.SanitizeHtml(str(value)) <SMALL>218 </SMALL> elif escaper_name == 'pprint': # for debugging <SMALL>219 </SMALL> value = '<pre>' + cgi.escape(pprint.pformat(value)) + '</pre>' <SMALL>220 </SMALL> <SMALL>221 </SMALL> if value is None: <SMALL>222 </SMALL> value = '' <SMALL>223 </SMALL> return value <SMALL>224 </SMALL> <SMALL>225 </SMALL> <SMALL>226 </SMALL>def _ExpandValue(var, specials, params, name, default): <SMALL>227 </SMALL> """Expand one value. <SMALL>228 </SMALL> <SMALL>229 </SMALL> This expands the <field>.<field>...<field> part of the variable <SMALL>230 </SMALL> expansion. A field may be of the form *<param> to use the value <SMALL>231 </SMALL> of a parameter as the field name. <SMALL>232 </SMALL> """ <SMALL>233 </SMALL> if var == '_key': <SMALL>234 </SMALL> return name <SMALL>235 </SMALL> elif var == '_this': <SMALL>236 </SMALL> return params <SMALL>237 </SMALL> if var.startswith('_'): <SMALL>238 </SMALL> value = specials <SMALL>239 </SMALL> else: <SMALL>240 </SMALL> value = params <SMALL>241 </SMALL> <SMALL>242 </SMALL> for v in var.split('.'): <SMALL>243 </SMALL> if v == '*_this': <SMALL>244 </SMALL> v = params <SMALL>245 </SMALL> if v.startswith('*'): <SMALL>246 </SMALL> v = _GetValue(specials['_params'], v[1:]) <SMALL>247 </SMALL> if operator.isSequenceType(v): <SMALL>248 </SMALL> v = v[0] # reduce repeated url param to single value <SMALL>249 </SMALL> value = _GetValue(value, str(v), default) <SMALL>250 </SMALL> return value <SMALL>251 </SMALL> <SMALL>252 </SMALL> <SMALL>253 </SMALL>def _GetValue(collection, index, default=''): <SMALL>254 </SMALL> """Gets a single indexed value out of a collection. <SMALL>255 </SMALL> <SMALL>256 </SMALL> The index is either a key in a mapping or a numeric index into <SMALL>257 </SMALL> a sequence. <SMALL>258 </SMALL> <SMALL>259 </SMALL> Returns: <SMALL>260 </SMALL> value <SMALL>261 </SMALL> """ <SMALL>262 </SMALL> if operator.isMappingType(collection) and index in collection: <SMALL>263 </SMALL> value = collection[index] <SMALL>264 </SMALL> elif (operator.isSequenceType(collection) and index.isdigit() and <SMALL>265 </SMALL> int(index) < len(collection)): <SMALL>266 </SMALL> value = collection[int(index)] <SMALL>267 </SMALL> else: <SMALL>268 </SMALL> value = default <SMALL>269 </SMALL> return value <SMALL>270 </SMALL> <SMALL>271 </SMALL> <SMALL>272 </SMALL>def _Cond(test, if_true, if_false): <SMALL>273 </SMALL> """Substitute for 'if_true if test else if_false' in Python 2.4.""" <SMALL>274 </SMALL> if test: <SMALL>275 </SMALL> return if_true <SMALL>276 </SMALL> else: <SMALL>277 </SMALL> return if_false <SMALL>278 </SMALL> <SMALL>279 </SMALL> <SMALL>280 </SMALL>def _FindTag(template, open_marker, close_marker): <SMALL>281 </SMALL> """Finds a single tag. <SMALL>282 </SMALL> <SMALL>283 </SMALL> Args: <SMALL>284 </SMALL> template: the template to search. <SMALL>285 </SMALL> open_marker: the start of the tag (e.g., '{{'). <SMALL>286 </SMALL> close_marker: the end of the tag (e.g., '}}'). <SMALL>287 </SMALL> <SMALL>288 </SMALL> Returns: <SMALL>289 </SMALL> (tag, pos1, pos2) where the tag has the open and close markers <SMALL>290 </SMALL> stripped off and pos1 is the start of the tag and pos2 is the end of <SMALL>291 </SMALL> the tag. Returns (None, None, None) if there is no tag found. <SMALL>292 </SMALL> """ <SMALL>293 </SMALL> open_pos = template.find(open_marker) <SMALL>294 </SMALL> close_pos = template.find(close_marker, open_pos) <SMALL>295 </SMALL> if open_pos < 0 or close_pos < 0 or open_pos > close_pos: <SMALL>296 </SMALL> return (None, None, None) <SMALL>297 </SMALL> return (template[open_pos + len(open_marker):close_pos], <SMALL>298 </SMALL> open_pos, <SMALL>299 </SMALL> close_pos + len(close_marker)) <SMALL>300 </SMALL> <SMALL>301 </SMALL> <SMALL>302 </SMALL>def _Log(message): <SMALL>303 </SMALL> logging.warning('%s', message) <SMALL>304 </SMALL> print >>sys.stderr, message </PRE></BODY></HTML> |
| URL | http://google-gruyere.appspot.com/code/resources/dump.gtl |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 405 bytes. |
GET http://google-gruyere.appspot.com/code/resources/dump.gtl HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/code/?resources/dump.gtl Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 243 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: 7b20115ae030f495a6b3aaa34595cff1 Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 1337 |
| Response Body - size: 1,337 bytes. |
<HTML>
<STYLE> SMALL { color: #AAAAFF; font-size: 75% } BIG { color: #3333FF; font-size: 150%; font-weight: bold} </STYLE><BODY><PRE> <BIG>resources/dump.gtl</BIG> <SMALL> 1 </SMALL><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <SMALL> 2 </SMALL><!-- Copyright 2017 Google Inc. --> <SMALL> 3 </SMALL><html> <SMALL> 4 </SMALL><head> <SMALL> 5 </SMALL><title>Debug Dump</title> <SMALL> 6 </SMALL></head> <SMALL> 7 </SMALL><body> <SMALL> 8 </SMALL><table> <SMALL> 9 </SMALL> <tr> <SMALL> 10 </SMALL> <td valign='top'>_cookie:&nbsp;</td> <SMALL> 11 </SMALL> <td valign='top'>{{_cookie:pprint}}</td> <SMALL> 12 </SMALL> </tr> <SMALL> 13 </SMALL> <tr> <SMALL> 14 </SMALL> <td valign='top'>_profile:&nbsp;</td> <SMALL> 15 </SMALL> <td valign='top'>{{_profile:pprint}}</td> <SMALL> 16 </SMALL> </tr> <SMALL> 17 </SMALL> <tr> <SMALL> 18 </SMALL> <td valign='top'>_db:&nbsp;</td> <SMALL> 19 </SMALL> <td valign='top'>{{_db:pprint}}</td> <SMALL> 20 </SMALL> </tr> <SMALL> 21 </SMALL></table> <SMALL> 22 </SMALL></body> <SMALL> 23 </SMALL></html> </PRE></BODY></HTML> |
| URL | http://google-gruyere.appspot.com/code/resources/editprofile.gtl |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 419 bytes. |
GET http://google-gruyere.appspot.com/code/resources/editprofile.gtl HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/code/?resources/editprofile.gtl Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 243 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: 8c0c4184b5a7ce71183439e98835c508 Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 7482 |
| Response Body - size: 7,482 bytes. |
<HTML>
<STYLE> SMALL { color: #AAAAFF; font-size: 75% } BIG { color: #3333FF; font-size: 150%; font-weight: bold} </STYLE><BODY><PRE> <BIG>resources/editprofile.gtl</BIG> <SMALL> 1 </SMALL><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <SMALL> 2 </SMALL><!-- Copyright 2017 Google Inc. --> <SMALL> 3 </SMALL><html> <SMALL> 4 </SMALL><head> <SMALL> 5 </SMALL><title>Gruyere: Profile</title> <SMALL> 6 </SMALL>[[include:base.css]][[/include:base.css]] <SMALL> 7 </SMALL></head> <SMALL> 8 </SMALL> <SMALL> 9 </SMALL><body> <SMALL> 10 </SMALL>[[include:menubar.gtl]][[/include:menubar.gtl]] <SMALL> 11 </SMALL><div> <SMALL> 12 </SMALL><h2>Gruyere: Profile</h2> <SMALL> 13 </SMALL></div> <SMALL> 14 </SMALL> <SMALL> 15 </SMALL><div class='content'> <SMALL> 16 </SMALL>[[if:_cookie.is_admin]] <SMALL> 17 </SMALL> <h3>Add a new account or edit an existing account.</h3> <SMALL> 18 </SMALL>[[/if:_cookie.is_admin]] <SMALL> 19 </SMALL>[[if:!_cookie.is_admin]] <SMALL> 20 </SMALL> <h3>Edit your profile.</h3> <SMALL> 21 </SMALL>[[/if:!_cookie.is_admin]] <SMALL> 22 </SMALL>[[if:_message]] <SMALL> 23 </SMALL><div class='message'>{{_message}}</div> <SMALL> 24 </SMALL>[[/if:_message]] <SMALL> 25 </SMALL> <SMALL> 26 </SMALL><form method='get' action='/{{_unique_id}}/saveprofile'> <SMALL> 27 </SMALL><input type='hidden' name='action' value='update'> <SMALL> 28 </SMALL><table> <SMALL> 29 </SMALL> <tr><td> <SMALL> 30 </SMALL> User id: <SMALL> 31 </SMALL> </td><td> <SMALL> 32 </SMALL>[[if:_cookie.is_admin]] <SMALL> 33 </SMALL> [[if:uid]] <SMALL> 34 </SMALL> <input type='hidden' name='uid' value='{{uid.0}}'> <SMALL> 35 </SMALL> {{uid.0}} <SMALL> 36 </SMALL> [[/if:uid]] <SMALL> 37 </SMALL> [[if:!uid]] <SMALL> 38 </SMALL> <input type='hidden' name='uid' value='{{_cookie.uid}}'> <SMALL> 39 </SMALL> {{_cookie.uid}} <SMALL> 40 </SMALL> [[/if:!uid]] <SMALL> 41 </SMALL>[[/if:_cookie.is_admin]] <SMALL> 42 </SMALL>[[if:!_cookie.is_admin]] <SMALL> 43 </SMALL> [[if:_cookie.uid]] <SMALL> 44 </SMALL> {{_cookie.uid}} <SMALL> 45 </SMALL> [[/if:_cookie.uid]] <SMALL> 46 </SMALL> [[if:!_cookie.uid]] <SMALL> 47 </SMALL> &lt;not logged in&gt; <SMALL> 48 </SMALL> [[/if:!_cookie.uid]] <SMALL> 49 </SMALL>[[/if:!_cookie.is_admin]] <SMALL> 50 </SMALL> </td></tr> <SMALL> 51 </SMALL><tr><td> <SMALL> 52 </SMALL> User name: <SMALL> 53 </SMALL> </td><td> <SMALL> 54 </SMALL> <input type='text' <SMALL> 55 </SMALL> value='[[if:uid]]{{_db.*uid.name:text}}[[/if:uid]][[if:!uid]]{{_profile.name:text}}[[/if:!uid]]' <SMALL> 56 </SMALL> name='name' maxlength='16'> <SMALL> 57 </SMALL></td></tr> <SMALL> 58 </SMALL><tr><td> <SMALL> 59 </SMALL> OLD Password: <SMALL> 60 </SMALL> </td><td> <SMALL> 61 </SMALL> <input type='password' name='oldpw'> <SMALL> 62 </SMALL> </td><td> <SMALL> 63 </SMALL></td></tr> <SMALL> 64 </SMALL><tr><td> <SMALL> 65 </SMALL> NEW Password: <SMALL> 66 </SMALL> </td><td> <SMALL> 67 </SMALL> <input type='password' name='pw'> <SMALL> 68 </SMALL> <br><span style="color:red"><b>WARNING: Gruyere is not secure.<br> <SMALL> 69 </SMALL> Do not use a password that you use for any real service.</b></span> <SMALL> 70 </SMALL></td></tr> <SMALL> 71 </SMALL><tr><td> <SMALL> 72 </SMALL> Icon: <SMALL> 73 </SMALL> </td><td> <SMALL> 74 </SMALL> <input type='text' <SMALL> 75 </SMALL> value='[[if:uid]]{{_db.*uid.icon:text}}[[/if:uid]][[if:!uid]]{{_profile.icon:text}}[[/if:!uid]]' <SMALL> 76 </SMALL> value='{{_profile.icon:text}}' <SMALL> 77 </SMALL> name='icon'> <SMALL> 78 </SMALL> (32x32 image, URL to image location) <SMALL> 79 </SMALL></td></tr> <SMALL> 80 </SMALL><tr><td> <SMALL> 81 </SMALL> Homepage: <SMALL> 82 </SMALL> </td><td> <SMALL> 83 </SMALL> <input type='text' size='50' <SMALL> 84 </SMALL> value='[[if:uid]]{{_db.*uid.web_site:text}}[[/if:uid]][[if:!uid]]{{_profile.web_site:text}}[[/if:!uid]]' <SMALL> 85 </SMALL> name='web_site'> <SMALL> 86 </SMALL></td></tr> <SMALL> 87 </SMALL><tr><td> <SMALL> 88 </SMALL> Profile Color: <SMALL> 89 </SMALL> </td><td> <SMALL> 90 </SMALL> <input type='text' <SMALL> 91 </SMALL> value='[[if:uid]]{{_db.*uid.color:text}}[[/if:uid]][[if:!uid]]{{_profile.color:text}}[[/if:!uid]]' <SMALL> 92 </SMALL> name='color'> <SMALL> 93 </SMALL></td></tr> <SMALL> 94 </SMALL><tr><td> <SMALL> 95 </SMALL> Private Snippet: <SMALL> 96 </SMALL> </td><td> <SMALL> 97 </SMALL> <textarea name='private_snippet' rows='10' style='width:100%'>[[if:uid]]{{_db.*uid.private_snippet}}[[/if:uid]][[if:!uid]]{{_profile.private_snippet}}[[/if:!uid]]</textarea> <SMALL> 98 </SMALL></td></tr> <SMALL> 99 </SMALL>[[if:_cookie.is_admin]] <SMALL>100 </SMALL><tr><td> <SMALL>101 </SMALL> Is admin: <SMALL>102 </SMALL> </td><td> <SMALL>103 </SMALL> <input type='radio' <SMALL>104 </SMALL> [[if:uid]][[if:_db.*uid.is_admin]]checked[[/if:_db.*uid.is_admin]][[/if:uid]] <SMALL>105 </SMALL> [[if:!uid]][[if:_profile.is_admin]]checked[[/if:_profile.is_admin]][[/if:!uid]] <SMALL>106 </SMALL> name='is_admin' value='True'>Yes <SMALL>107 </SMALL> <input type='radio' <SMALL>108 </SMALL> [[if:uid]][[if:!_db.*uid.is_admin]]checked[[/if:!_db.*uid.is_admin]][[/if:uid]] <SMALL>109 </SMALL> [[if:!uid]][[if:!_profile.is_admin]]checked[[/if:!_profile.is_admin]][[/if:!uid]] <SMALL>110 </SMALL> name='is_admin' value='False'>No <SMALL>111 </SMALL></td></tr> <SMALL>112 </SMALL><tr><td> <SMALL>113 </SMALL> Is author: <SMALL>114 </SMALL> </td><td> <SMALL>115 </SMALL> <input type='radio' <SMALL>116 </SMALL> [[if:uid]][[if:_db.*uid.is_author]]checked[[/if:_db.*uid.is_author]][[/if:uid]] <SMALL>117 </SMALL> [[if:!uid]][[if:_profile.is_author]]checked[[/if:_profile.is_author]][[/if:!uid]] <SMALL>118 </SMALL> name='is_author' value='True'>Yes <SMALL>119 </SMALL> <input type='radio' <SMALL>120 </SMALL> [[if:uid]][[if:!_db.*uid.is_author]]checked[[/if:!_db.*uid.is_author]][[/if:uid]] <SMALL>121 </SMALL> [[if:!uid]][[if:!_profile.is_author]]checked[[/if:!_profile.is_author]][[/if:!uid]] <SMALL>122 </SMALL> name='is_author' value='False'>No <SMALL>123 </SMALL></td></tr> <SMALL>124 </SMALL>[[/if:_cookie.is_admin]] <SMALL>125 </SMALL> <SMALL>126 </SMALL><tr><td></td><td align='left'> <SMALL>127 </SMALL> <input type='submit' value='Update'> <SMALL>128 </SMALL></td></tr> <SMALL>129 </SMALL></table> <SMALL>130 </SMALL></form> <SMALL>131 </SMALL></div> <SMALL>132 </SMALL> <SMALL>133 </SMALL></body> <SMALL>134 </SMALL> <SMALL>135 </SMALL></html> <SMALL>136 </SMALL> </PRE></BODY></HTML> |
| URL | http://google-gruyere.appspot.com/code/resources/error.gtl |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 407 bytes. |
GET http://google-gruyere.appspot.com/code/resources/error.gtl HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/code/?resources/error.gtl Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 242 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: c5164fec213dd28a8a633a716d54cbfd Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 975 |
| Response Body - size: 975 bytes. |
<HTML>
<STYLE> SMALL { color: #AAAAFF; font-size: 75% } BIG { color: #3333FF; font-size: 150%; font-weight: bold} </STYLE><BODY><PRE> <BIG>resources/error.gtl</BIG> <SMALL> 1 </SMALL><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <SMALL> 2 </SMALL><!-- Copyright 2017 Google Inc. --> <SMALL> 3 </SMALL><html> <SMALL> 4 </SMALL><head> <SMALL> 5 </SMALL><title>Gruyere: Error</title> <SMALL> 6 </SMALL>[[include:base.css]][[/include:base.css]] <SMALL> 7 </SMALL></head> <SMALL> 8 </SMALL> <SMALL> 9 </SMALL><body> <SMALL> 10 </SMALL>[[include:menubar.gtl]][[/include:menubar.gtl]] <SMALL> 11 </SMALL> <SMALL> 12 </SMALL>[[if:_message]] <SMALL> 13 </SMALL><div class='message'>{{_message}}</div> <SMALL> 14 </SMALL>[[/if:_message]] <SMALL> 15 </SMALL> <SMALL> 16 </SMALL></body> <SMALL> 17 </SMALL> <SMALL> 18 </SMALL></html> </PRE></BODY></HTML> |
| URL | http://google-gruyere.appspot.com/code/resources/feed.gtl |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 405 bytes. |
GET http://google-gruyere.appspot.com/code/resources/feed.gtl HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/code/?resources/feed.gtl Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 243 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: b57dc5f16294ff477e59913fcd3d04a2 Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 1769 |
| Response Body - size: 1,769 bytes. |
<HTML>
<STYLE> SMALL { color: #AAAAFF; font-size: 75% } BIG { color: #3333FF; font-size: 150%; font-weight: bold} </STYLE><BODY><PRE> <BIG>resources/feed.gtl</BIG> <SMALL> 1 </SMALL>{{# Copyright 2017 Google Inc. }} <SMALL> 2 </SMALL>_feed(( <SMALL> 3 </SMALL>{{# with a uid parameter, get one user's snippets <SMALL> 4 </SMALL> returns [name, snippet, ...] <SMALL> 5 </SMALL>The first entry is the user's name and the remaining entries are <SMALL> 6 </SMALL>the user's snippets, in order from most recent to least recent. <SMALL> 7 </SMALL>}} <SMALL> 8 </SMALL>[[if:uid]] <SMALL> 9 </SMALL>[ <SMALL> 10 </SMALL>"[[if:_db.*uid]]{{_db.*uid.name}}[[/if:_db.*uid]][[if:!_db.*uid]]{{uid.0}}[[/if:!_db.*uid]]" <SMALL> 11 </SMALL>[[if:_db.*uid.snippets.0]][[for:_db.*uid.snippets]] <SMALL> 12 </SMALL>,"{{_this:html}}" <SMALL> 13 </SMALL>[[/for:_db.*uid.snippets]][[/if:_db.*uid.snippets.0]] <SMALL> 14 </SMALL>] <SMALL> 15 </SMALL>[[/if:uid]] <SMALL> 16 </SMALL>{{# without a uid parameter, get one snippet from each user <SMALL> 17 </SMALL> returns {'private_snippet':snippet, user:snippet, ...} <SMALL> 18 </SMALL>The first entry is the logged in user's private snippet. <SMALL> 19 </SMALL>The rest of the entries are all the other users' most recent snippet. <SMALL> 20 </SMALL>}} <SMALL> 21 </SMALL>[[if:!uid]] <SMALL> 22 </SMALL>{ <SMALL> 23 </SMALL>"private_snippet": <SMALL> 24 </SMALL> "{{_profile.private_snippet:html}}" <SMALL> 25 </SMALL>[[for:_db]] <SMALL> 26 </SMALL>[[if:is_author]][[if:snippets.0]],"{{_key}}": <SMALL> 27 </SMALL> "{{snippets.0:html}}"[[/if:snippets.0]][[/if:is_author]][[/for:_db]] <SMALL> 28 </SMALL>} <SMALL> 29 </SMALL>[[/if:!uid]] <SMALL> 30 </SMALL>)) </PRE></BODY></HTML> |
| URL | http://google-gruyere.appspot.com/code/resources/home.gtl |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 405 bytes. |
GET http://google-gruyere.appspot.com/code/resources/home.gtl HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/code/?resources/home.gtl Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 243 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: d1362b44f8aba0a5a87e45f8e9aa022b Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 3533 |
| Response Body - size: 3,533 bytes. |
<HTML>
<STYLE> SMALL { color: #AAAAFF; font-size: 75% } BIG { color: #3333FF; font-size: 150%; font-weight: bold} </STYLE><BODY><PRE> <BIG>resources/home.gtl</BIG> <SMALL> 1 </SMALL><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <SMALL> 2 </SMALL><!-- Copyright 2017 Google Inc. --> <SMALL> 3 </SMALL><html> <SMALL> 4 </SMALL><head> <SMALL> 5 </SMALL><title>Gruyere: Home</title> <SMALL> 6 </SMALL>[[include:base.css]][[/include:base.css]] <SMALL> 7 </SMALL><script src="/{{_unique_id}}/lib.js" text="text/javascript"> <SMALL> 8 </SMALL></script> <SMALL> 9 </SMALL></head> <SMALL> 10 </SMALL> <SMALL> 11 </SMALL><body> <SMALL> 12 </SMALL>[[include:menubar.gtl]][[/include:menubar.gtl]] <SMALL> 13 </SMALL><div> <SMALL> 14 </SMALL><h2 class='has-refresh'>Gruyere: Home</h2> <SMALL> 15 </SMALL><div class='refresh'><a class='button' onclick='_refreshHome("{{_unique_id}}")' href='#'>Refresh</a></div> <SMALL> 16 </SMALL></div> <SMALL> 17 </SMALL><div class='content'> <SMALL> 18 </SMALL><table width='100%'> <SMALL> 19 </SMALL>[[if:_profile.private_snippet]] <SMALL> 20 </SMALL> <tr> <SMALL> 21 </SMALL> <td></td> <SMALL> 22 </SMALL> <td> <SMALL> 23 </SMALL> Private&nbsp;snippet&nbsp; <SMALL> 24 </SMALL> </td> <SMALL> 25 </SMALL> <td> <SMALL> 26 </SMALL> <a class='button' id='show' onclick='_showHide("show", "hide")' href='#'>Show &#9658;</a> <SMALL> 27 </SMALL> <span id='hide' style='display: none;'> <SMALL> 28 </SMALL> <a class='button' onclick='_showHide("show", "hide")' href='#'>Hide &#9668;</a> &nbsp; <SMALL> 29 </SMALL> <span id='private_snippet'>{{_profile.private_snippet:html}}</span></span> <SMALL> 30 </SMALL> </td> <SMALL> 31 </SMALL> </tr> <SMALL> 32 </SMALL> <tr><td colspan='3'><hr></td></tr> <SMALL> 33 </SMALL>[[/if:_profile.private_snippet]] <SMALL> 34 </SMALL> <tr><td colspan='3'><b>Most recent snippets:</b></td></tr> <SMALL> 35 </SMALL>[[for:_db]] <SMALL> 36 </SMALL>[[if:is_author]][[if:snippets.0]] <SMALL> 37 </SMALL> <tr> <SMALL> 38 </SMALL> <td> <SMALL> 39 </SMALL> [[if:icon]]<img alt='' height='32' width='32' src='{{icon:text}}'>[[/if:icon]] <SMALL> 40 </SMALL> </td> <SMALL> 41 </SMALL> <td> <SMALL> 42 </SMALL> <b><span style='color:{{color:text}}'>[[if:name]]{{name:text}}[[/if:name]][[if:!name]]{{_key}}[[/if:!name]]</span></b> <SMALL> 43 </SMALL> </td> <SMALL> 44 </SMALL> <td width='100%'><span id='{{_key}}'>{{snippets.0:html}}</span> <SMALL> 45 </SMALL> <br> <SMALL> 46 </SMALL> <a href='/{{_unique_id}}/snippets.gtl?uid={{_key}}'>All snippets</a>&nbsp; <SMALL> 47 </SMALL> <a href='{{web_site:text}}'>Homepage</a> <SMALL> 48 </SMALL> <br> <SMALL> 49 </SMALL> <br> <SMALL> 50 </SMALL> </td> <SMALL> 51 </SMALL> </tr> <SMALL> 52 </SMALL>[[/if:snippets.0]][[/if:is_author]] <SMALL> 53 </SMALL>[[/for:_db]] <SMALL> 54 </SMALL></table> <SMALL> 55 </SMALL></div> <SMALL> 56 </SMALL></body> <SMALL> 57 </SMALL></html> </PRE></BODY></HTML> |
| URL | http://google-gruyere.appspot.com/code/resources/manage.gtl |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 409 bytes. |
GET http://google-gruyere.appspot.com/code/resources/manage.gtl HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/code/?resources/manage.gtl Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 243 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: decb989a995899df2e83431d769ef82d Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 1499 |
| Response Body - size: 1,499 bytes. |
<HTML>
<STYLE> SMALL { color: #AAAAFF; font-size: 75% } BIG { color: #3333FF; font-size: 150%; font-weight: bold} </STYLE><BODY><PRE> <BIG>resources/manage.gtl</BIG> <SMALL> 1 </SMALL><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <SMALL> 2 </SMALL><!-- Copyright 2017 Google Inc. --> <SMALL> 3 </SMALL><html> <SMALL> 4 </SMALL><head> <SMALL> 5 </SMALL><title>Gruyere: Profile</title> <SMALL> 6 </SMALL>[[include:base.css]][[/include:base.css]] <SMALL> 7 </SMALL></head> <SMALL> 8 </SMALL> <SMALL> 9 </SMALL><body> <SMALL> 10 </SMALL>[[include:menubar.gtl]][[/include:menubar.gtl]] <SMALL> 11 </SMALL><h2>Gruyere: Manage the server</h2> <SMALL> 12 </SMALL> <SMALL> 13 </SMALL><div class='content'> <SMALL> 14 </SMALL><form method='get' action='/{{_unique_id}}/editprofile.gtl'> <SMALL> 15 </SMALL><p>Edit a user's profile: <SMALL> 16 </SMALL><input type='text' name='uid'> <SMALL> 17 </SMALL> <input type='submit' value='Edit'> <SMALL> 18 </SMALL></p> <SMALL> 19 </SMALL></form> <SMALL> 20 </SMALL><p><a href='/{{_unique_id}}/reset'>Reset the server</a></p> <SMALL> 21 </SMALL><p><a href='/{{_unique_id}}/quitserver'>Quit the server</a></p> <SMALL> 22 </SMALL></div> <SMALL> 23 </SMALL></body> <SMALL> 24 </SMALL> <SMALL> 25 </SMALL></html> </PRE></BODY></HTML> |
| URL | http://google-gruyere.appspot.com/code/resources/menubar.gtl |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 411 bytes. |
GET http://google-gruyere.appspot.com/code/resources/menubar.gtl HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/code/?resources/menubar.gtl Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 243 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: aca5b0a9978b6b226d98eede8d8160d2 Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 1889 |
| Response Body - size: 1,889 bytes. |
<HTML>
<STYLE> SMALL { color: #AAAAFF; font-size: 75% } BIG { color: #3333FF; font-size: 150%; font-weight: bold} </STYLE><BODY><PRE> <BIG>resources/menubar.gtl</BIG> <SMALL> 1 </SMALL>{{# Copyright 2017 Google Inc. }} <SMALL> 2 </SMALL><div class='menu'> <SMALL> 3 </SMALL> <span id='menu-left'> <SMALL> 4 </SMALL> <a href='/{{_unique_id}}/'>Home</a> <SMALL> 5 </SMALL> [[if:_cookie.uid]] <SMALL> 6 </SMALL> | <a href='/{{_unique_id}}/snippets.gtl'>My&nbsp;Snippets</a> <SMALL> 7 </SMALL> | <a href='/{{_unique_id}}/newsnippet.gtl'>New&nbsp;Snippet</a> <SMALL> 8 </SMALL> | <a href='/{{_unique_id}}/upload.gtl'>Upload</a> <SMALL> 9 </SMALL> [[/if:_cookie.uid]] <SMALL> 10 </SMALL> </span> <SMALL> 11 </SMALL> <span id='menu-right'> <SMALL> 12 </SMALL> [[if:_cookie.uid]] <SMALL> 13 </SMALL> <span class='menu-user'> <SMALL> 14 </SMALL> {{_profile.name:text}} &lt;{{_cookie.uid}}&gt; <SMALL> 15 </SMALL> </span> <SMALL> 16 </SMALL> [[if:_cookie.is_admin]] <SMALL> 17 </SMALL> | <a href='/{{_unique_id}}/manage.gtl'>Manage this server</a> <SMALL> 18 </SMALL> [[/if:_cookie.is_admin]] <SMALL> 19 </SMALL> | <a href='/{{_unique_id}}/editprofile.gtl'>Profile</a> <SMALL> 20 </SMALL> | <a href='/{{_unique_id}}/logout'>Sign out</a> <SMALL> 21 </SMALL> [[/if:_cookie.uid]] <SMALL> 22 </SMALL> [[if:!_cookie.uid]] <SMALL> 23 </SMALL> <a href='/{{_unique_id}}/login'>Sign in</a> <SMALL> 24 </SMALL> | <a href='/{{_unique_id}}/newaccount.gtl'>Sign up</a> <SMALL> 25 </SMALL> [[/if:!_cookie.uid]] <SMALL> 26 </SMALL> </span> <SMALL> 27 </SMALL></div> </PRE></BODY></HTML> |
| URL | http://google-gruyere.appspot.com/code/sanitize.py |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 391 bytes. |
GET http://google-gruyere.appspot.com/code/sanitize.py HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/code/?sanitize.py Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 243 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: 4480b1f9f961edd2d73e281dec492f72 Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 4996 |
| Response Body - size: 4,996 bytes. |
<HTML>
<STYLE> SMALL { color: #AAAAFF; font-size: 75% } BIG { color: #3333FF; font-size: 150%; font-weight: bold} </STYLE><BODY><PRE> <BIG>sanitize.py</BIG> <SMALL> 1 </SMALL>"""HTML sanitizer for Gruyere, a web application with holes. <SMALL> 2 </SMALL> <SMALL> 3 </SMALL>Copyright 2017 Google Inc. All rights reserved. <SMALL> 4 </SMALL> <SMALL> 5 </SMALL>This code is licensed under the https://creativecommons.org/licenses/by-nd/3.0/us/ <SMALL> 6 </SMALL>Creative Commons Attribution-No Derivative Works 3.0 United States license. <SMALL> 7 </SMALL> <SMALL> 8 </SMALL>DO NOT COPY THIS CODE! <SMALL> 9 </SMALL> <SMALL> 10 </SMALL>This application is a small self-contained web application with numerous <SMALL> 11 </SMALL>security holes. It is provided for use with the Web Application Exploits and <SMALL> 12 </SMALL>Defenses codelab. You may modify the code for your own use while doing the <SMALL> 13 </SMALL>codelab but you may not distribute the modified code. Brief excerpts of this <SMALL> 14 </SMALL>code may be used for educational or instructional purposes provided this <SMALL> 15 </SMALL>notice is kept intact. By using Gruyere you agree to the Terms of Service <SMALL> 16 </SMALL>https://www.google.com/intl/en/policies/terms/ <SMALL> 17 </SMALL>""" <SMALL> 18 </SMALL> <SMALL> 19 </SMALL>__author__ = 'Bruce Leban' <SMALL> 20 </SMALL> <SMALL> 21 </SMALL># system modules <SMALL> 22 </SMALL>import re <SMALL> 23 </SMALL> <SMALL> 24 </SMALL> <SMALL> 25 </SMALL>def SanitizeHtml(s): <SMALL> 26 </SMALL> """Makes html safe for embedding in a document. <SMALL> 27 </SMALL> <SMALL> 28 </SMALL> Filters the html to exclude all but a small subset of html by <SMALL> 29 </SMALL> removing script tags/attributes. <SMALL> 30 </SMALL> <SMALL> 31 </SMALL> Args: <SMALL> 32 </SMALL> s: some html to sanitize. <SMALL> 33 </SMALL> <SMALL> 34 </SMALL> Returns: <SMALL> 35 </SMALL> The html with all unsafe html removed. <SMALL> 36 </SMALL> """ <SMALL> 37 </SMALL> processed = '' <SMALL> 38 </SMALL> while s: <SMALL> 39 </SMALL> start = s.find('<') <SMALL> 40 </SMALL> if start >= 0: <SMALL> 41 </SMALL> end = s.find('>', start) <SMALL> 42 </SMALL> if end >= 0: <SMALL> 43 </SMALL> before = s[:start] <SMALL> 44 </SMALL> tag = s[start:end+1] <SMALL> 45 </SMALL> after = s[end+1:] <SMALL> 46 </SMALL> else: <SMALL> 47 </SMALL> before = s[:start] <SMALL> 48 </SMALL> tag = s[start:] <SMALL> 49 </SMALL> after = '' <SMALL> 50 </SMALL> else: <SMALL> 51 </SMALL> before = s <SMALL> 52 </SMALL> tag = '' <SMALL> 53 </SMALL> after = '' <SMALL> 54 </SMALL> <SMALL> 55 </SMALL> processed += before + _SanitizeTag(tag) <SMALL> 56 </SMALL> s = after <SMALL> 57 </SMALL> return processed <SMALL> 58 </SMALL> <SMALL> 59 </SMALL> <SMALL> 60 </SMALL>TAG_RE = re.compile(r'<(.*?)(\s|>)') # matches the start of an html tag <SMALL> 61 </SMALL> <SMALL> 62 </SMALL> <SMALL> 63 </SMALL>def _SanitizeTag(t): <SMALL> 64 </SMALL> """Sanitizes a single html tag. <SMALL> 65 </SMALL> <SMALL> 66 </SMALL> This does both a 'whitelist' for <SMALL> 67 </SMALL> the allowed tags and a 'blacklist' for the disallowed attributes. <SMALL> 68 </SMALL> <SMALL> 69 </SMALL> Args: <SMALL> 70 </SMALL> t: a tag to sanitize. <SMALL> 71 </SMALL> <SMALL> 72 </SMALL> Returns: <SMALL> 73 </SMALL> a safe tag. <SMALL> 74 </SMALL> """ <SMALL> 75 </SMALL> allowed_tags = [ <SMALL> 76 </SMALL> 'a', 'b', 'big', 'br', 'center', 'code', 'em', 'h1', 'h2', 'h3', <SMALL> 77 </SMALL> 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'li', 'ol', 'p', 's', 'small', <SMALL> 78 </SMALL> 'span', 'strong', 'table', 'td', 'tr', 'u', 'ul', <SMALL> 79 </SMALL> ] <SMALL> 80 </SMALL> disallowed_attributes = [ <SMALL> 81 </SMALL> 'onblur', 'onchange', 'onclick', 'ondblclick', 'onfocus', <SMALL> 82 </SMALL> 'onkeydown', 'onkeypress', 'onkeyup', 'onload', 'onmousedown', <SMALL> 83 </SMALL> 'onmousemove', 'onmouseout', 'onmouseup', 'onreset', <SMALL> 84 </SMALL> 'onselect', 'onsubmit', 'onunload' <SMALL> 85 </SMALL> ] <SMALL> 86 </SMALL> <SMALL> 87 </SMALL> # Extract the tag name and make sure it's allowed. <SMALL> 88 </SMALL> if t.startswith('</'): <SMALL> 89 </SMALL> return t <SMALL> 90 </SMALL> m = TAG_RE.match(t) <SMALL> 91 </SMALL> if m is None: <SMALL> 92 </SMALL> return t <SMALL> 93 </SMALL> tag_name = m.group(1) <SMALL> 94 </SMALL> if tag_name not in allowed_tags: <SMALL> 95 </SMALL> t = t[:m.start(1)] + 'blocked' + t[m.end(1):] <SMALL> 96 </SMALL> <SMALL> 97 </SMALL> # This is a bit heavy handed but we want to be sure we don't <SMALL> 98 </SMALL> # allow any to get through. <SMALL> 99 </SMALL> for a in disallowed_attributes: <SMALL>100 </SMALL> t = t.replace(a, 'blocked') <SMALL>101 </SMALL> return t </PRE></BODY></HTML> |
| URL | http://google-gruyere.appspot.com/part1 |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 363 bytes. |
GET http://google-gruyere.appspot.com/part1 HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/ Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 244 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: 2299c1a98044c5397b549a259ee919b9 Date: Tue, 06 Feb 2024 17:42:18 GMT Server: Google Frontend Content-Length: 13686 |
| Response Body - size: 13,686 bytes. |
<HTML><HEAD><META http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<TITLE>Web Application Exploits and Defenses</TITLE> <LINK type="text/css" rel="stylesheet" href="../static/codelab.css"> <!--MARK-Z--> <SCRIPT> function toggleBlock(heading, whichID) { var image = heading.childNodes[0]; var block = document.getElementById(whichID); if (block) { if (getDisplay(block) == 'block') { block.style.display = 'none'; image.src = 'static/closed.gif'; } else { // "none" or "" block.style.display = 'block'; image.src = 'static/open.gif'; } } } function getDisplay(block) { var value = block.style.display; if (!value) { if (document.defaultView) { var computedStyle = document.defaultView.getComputedStyle(block, ""); value = computedStyle.getPropertyValue('display'); } else if (block.currentStyle) { value = block.currentStyle.display; } } return value; } </SCRIPT> </HEAD> <BODY bgcolor="#ffffff"> <DIV class="banner"></DIV> <H1><FONT size="+2"><IMG src="/static/gruyere-78.png" style="vertical-align:middle"> Web Application Exploits and Defenses (Part 1)</FONT> </H1> <P> A Codelab by Bruce Leban, Mugdha Bendre, and Parisa Tabriz </P> <DIV class="printable"> <DIV class="column1"> Table of Contents <UL> <LI class="L1" style="padding-top:0pt"> <A href="/#0__hackers">Beat the hackers</A></LI> <LI class="L1" style="padding-top:0pt"> <A href="/#0__gruyere">Gruyere</A></LI> <LI class="L1"> <A href="/part1#1__setup">Set-up</A> <UL> <LI class="L2"> <A href="/part1#1__reset_button">Reset Button</A></LI> <LI class="L2"> <A href="/part1#1__about_the_code">About the Code</A></LI> <LI class="L2"> <A href="/part1#1__features_and_technologies">Features and Technologies</A></LI> </UL></LI> <LI class="L1"> <A href="/part1#1__using_gruyere">Using Gruyere</A></LI> <LI class="L1"> <A href="/part2#2__cross_site_scripting">Cross-Site Scripting (XSS)</A> <UL> <LI class="L2"> <A href="/part2#2__xss_challenge">XSS Challenges</A></LI> <LI class="L2"> <A href="/part2#2__file_upload_xss">File Upload XSS</A></LI> <LI class="L2"> <A href="/part2#2__reflected_xss">Reflected XSS</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss">Stored XSS</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss_via_html_attribute">Stored XSS via HTML Attribute</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss_via_ajax">Stored XSS via AJAX</A></LI> <LI class="L2"> <A href="/part2#2__reflected_xss_via_ajax">Reflected XSS via AJAX</A></LI> <LI class="L2"> <A href="/part2#2__more_about_xss">More about XSS</A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__client_state_manipulation">Client-State Manipulation</A> <UL> <LI class="L2"> <A href="/part3#3__elevation_of_privilege"> Elevation of Privilege </A></LI> <LI class="L2"> <A href="/part3#3__cookie_manipulation"> Cookie Manipulation </A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__cross_site_request_forgery">Cross-Site Request Forgery (XSRF)</A> <UL> <LI class="L2"> <A href="/part3#3__xsrf_challenge">XSRF Challenge</A></LI> <LI class="L2"> <A href="/part3#3__more_about_preventing_xsrf">More about preventing XSRF</A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__cross_site_script_inclusion">Cross Site Script Inclusion (XSSI)</A> <UL> <LI class="L2"> <A href="/part3#3__xssi_challenge">XSSI Challenge</A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__path_traversal">Path Traversal</A> <UL> <LI class="L2"> <A href="/part4#4__information_disclosure_path_traversal">Information disclosure via path traversal </A></LI> <LI class="L2"> <A href="/part4#4__data_tampering_path_traversal">Data tampering via path traversal </A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__denial_of_service">Denial of Service</A> <UL> <LI class="L2"> <A href="/part4#4__dos_quit_server">DoS - Quit the Server</A></LI> <LI class="L2"> <A href="/part4#4__dos_overload_server">DoS - Overloading the Server</A></LI> <LI class="L2"> <A href="/part4#4__more_dos">More on Denial of Service</A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__code_execution">Code Execution</A> <UL> <LI class="L2"> <A href="/part4#4__code_execution_challenge">Code Execution Challenge</A></LI> <LI class="L2"> <A href="/part4#4__more_code_execution">More on Remote Code Execution</A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__configuration_vulnerabilities">Configuration Vulnerabilities</A> <UL> <LI class="L2"> <A href="/part5#5__information_disclosure_config_1">Information disclosure #1</A></LI> <LI class="L2"> <A href="/part5#5__information_disclosure_config_2">Information disclosure #2</A></LI> <LI class="L2"> <A href="/part5#5__information_disclosure_bug_3">Information disclosure #3 </A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__ajax_vulnerabilities">AJAX vulnerabilities</A> <UL> <LI class="L2"> <A href="/part5#5__dos_via_ajax">DoS via AJAX</A></LI> <LI class="L2"> <A href="/part5#5__phishing_via_ajax">Phishing via AJAX</A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__other_vulnerabilities"> Other Vulnerabilities </A> </H2> <UL> <LI class="L2"> <A href="/part5#5__buffer_and_integer_overflow">Buffer Overflow and Integer Overflow</A></LI> <LI class="L2"> <A href="/part5#5__sql_injection"> SQL Injection </A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__after_the_codelab">After the Codelab</A></LI> </UL> </DIV> <DIV class="column2"> <P></P> <P></P> <P> <NOAUTOLINK> </NOAUTOLINK></P> <H2><A name="1__setup"> </A> Setup </H2> <P> To access Gruyere, go to <CODE><A href="/start">https://google-gruyere.appspot.com/start</A></CODE>. AppEngine will start a new instance of Gruyere for you, assign it a unique id and redirect you to <CODE>https://google-gruyere.appspot.com/<!--do not replace-->123/</CODE> (where <CODE>123</CODE> is your unique id). Each instance of Gruyere is "sandboxed" from the other instances so your instance won't be affected by anyone else using Gruyere. You'll need to use your unique id instead of <CODE>123</CODE> in all the examples. If you want to share your instance of Gruyere with someone else (e.g., to show them a successful attack), just share the full URL with them including your unique id. </P> <P>The Gruyere source code is available online so that you can use it for white-box hacking. You can browse the source code at <A href="/code/">https://google-gruyere.appspot.com/code/</A> or download all the files from <A href="/gruyere-code.zip">https://google-gruyere.appspot.com/gruyere-code.zip</A>. If want to debug it or actually try fixing the bugs, you can download it and run it locally. You do not need to run Gruyere locally in order to do the lab. </P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'running_locally');"><IMG src="/static/closed.gif"> Running locally </H4> <DIV id="running_locally" style="display:none"> <P> <FONT color="#ff0000"> <B>WARNING:</B> Because Gruyere is very vulnerable, it includes some protection against being exploited by an external attacker when run locally. You'll see these parts of the code marked DO NOT CHANGE. Gruyere only accepts requests from localhost and uses a random unique id in the URL. However, it's difficult to fully protect against an external attack. And if you make changes to Gruyere you could make it more vulnerable to a real attack. Therefore, you should close other web pages while running Gruyere locally and you should make sure that no other user is logged in to the machine you are using. </FONT> </P> <P> To run Gruyere locally, you'll first need to install Python 2.7, if you don't already have it. Gruyere was developed and tested with version 2.7 and may not work with other versions of Python. You can download it from <A href="https://www.python.org/downloads/" target="_top">python.org</A>. Download Gruyere itself from <A href="/gruyere-code.zip">https://google-gruyere.appspot.com/gruyere-code.zip</A> and unpack it to your local disk. Then to run the application, simply type: </P> <P></P> <PRE> $ cd <gruyere-directory> $ ./gruyere.py</PRE> <P> You'll need to replace <CODE>google-gruyere.appspot.com</CODE> in all the examples with <CODE>localhost:8008</CODE> in addition to replacing <CODE>123</CODE> with your unique id. Note that the unique id appears in a different location. There are a few other small differences between running Gruyere locally vs. accessing the instance on App Engine. The most obvious is that the App Engine version runs in a limited sandbox. So if you do something that puts Gruyere into an infinite loop, the monitor will detect it and kill it. That might not happen when you run it locally, depending on what the loop is doing. </P> </DIV> </DIV> <BR> <H3><A name="1__reset_button"> </A> Reset Button </H3> As noted above, each instance is sandboxed so it can't consume infinite resources and it can't interfere with anyone else's instance. Notwithstanding that, it is possible to put your Gruyere instance into a state where it is completely unusable. If that happens, you can push a magic "reset button" to wipe out all the data in your instance and start from scratch. To do this, visit this URL with your instance id: <PRE> https://google-gruyere.appspot.com/resetbutton/382665580745386307547168512335551204731 </PRE> <BR> <H3><A name="1__about_the_code"> </A> About the Code </H3> <P> Gruyere is small and compact. Here is a quick rundown of the application code: </P><UL> <LI> <CODE><A href="/code/?gruyere.py">gruyere.py</A></CODE> is the main Gruyere web server </LI> <LI> <CODE><A href="/code/?data.py">data.py</A></CODE> stores the default data in the database. There is an administrator account and two default users. </LI> <LI> <CODE><A href="/code/?gtl.py">gtl.py</A></CODE> is the Gruyere template language </LI> <LI> <CODE><A href="/code/?sanitize.py">sanitize.py</A></CODE> is the Gruyere module used for sanitizing HTML to protect the application from security holes. </LI> <LI> <CODE><A href="/code/">resources/...</A></CODE> holds all template files, images, CSS, etc. </LI> </UL> <P></P> <BR> <H3><A name="1__features_and_technologies"> </A> Features and Technologies </H3> <P> Gruyere includes a number of special features and technologies which add attack surface. We'll highlight them here so you'll be aware of them as you try to attack it. Each of these introduces new vulnerabilities. </P> <P></P> <UL> <LI> HTML in Snippets: Users can include a limited subset of HTML in their snippets. </LI> <LI> File upload: Users can upload files to the server, e.g., to include pictures in their snippets. </LI> <LI> Web administration: System administrators can manage the system using a web interface. </LI> <LI> New accounts: Users can create their own accounts. </LI> <LI> Template language: Gruyere Template Language(GTL) is a new language that makes writing web pages easy as the templates connect directly to the database. Documentation for GTL can be found in <CODE><A href="/code/?gtl.py">gruyere/gtl.py</A></CODE>. </LI> <LI> AJAX: Gruyere uses AJAX to implement refresh on the home and snippets page. You should ignore the AJAX parts of Gruyere except for the challenges that specifically tell you to focus on AJAX. <UL> <LI> In a real application, refresh would probably happen automatically, but in Gruyere we've made it manual so that you can be in complete control while you are working with it. When you click the refresh link, Gruyere fetches <CODE><A href="/code/?resources/feed.gtl">feed.gtl</A></CODE> which contains refresh data for the current page and then client-side script uses the browser DOM API (Document Object Model) to insert the new snippets into the page. Since AJAX runs code on the client side, this script is visible to attackers who do not have access to your source code. </LI> </UL> </LI> </UL> <P></P> <BR> <H2><A name="1__using_gruyere"> </A> Using Gruyere </H2> <P> To familiarize yourself with the features of Gruyere, complete the following tasks: </P> <P></P> <UL> <LI> View another user's snippets by following the "All snippets" link on the main page. Also check out what they have their Homepage set to. </LI> <LI> Sign up for an account for yourself to use when hacking. <B>Do not use the same password for your Gruyere account as you use for any real service.</B> </LI> <LI> Fill in your account's profile, including a private snippet and an icon that will be displayed by your name. </LI> <LI> Create a snippet (via "New Snippet") containing your favorite joke. </LI> <LI> Upload a file (via "Upload") to your account. </LI> </UL> <P></P> <P> This covers the basic features provided by Gruyere. Now let's break them! </P> <BR><P></P> <FONT SIZE="+2"> <A href="/part2">Continue >></A> </FONT><BR> <BR> <P style="font-size:x-small"> © Google 2017 <A href="https://www.google.com/intl/en/policies/terms/">Terms of Service</A> <BR>The code portions of this codelab are licensed under the Creative Commons Attribution-No Derivative Works 3.0 United States license <<A href="https://creativecommons.org/licenses/by-nd/3.0/us/">https://creativecommons.org/licenses/by-nd/3.0/us</A>>. Brief excerpts of the code may be used for educational or instructional purposes provided this notice is kept intact. Except as otherwise noted the remainder of this codelab is licensed under the Creative Commons Attribution 3.0 United States license <<A href="https://creativecommons.org/licenses/by/3.0/us/">https://creativecommons.org/licenses/by/3.0/us</A>>. </P> </BODY></HTML> |
| URL | http://google-gruyere.appspot.com/part2 |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 363 bytes. |
GET http://google-gruyere.appspot.com/part2 HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/ Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 244 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: fb541a29ec594f4af66ea0f3cff34252 Date: Tue, 06 Feb 2024 17:42:21 GMT Server: Google Frontend Content-Length: 37564 |
| Response Body - size: 37,564 bytes. |
<HTML><HEAD><META http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<TITLE>Web Application Exploits and Defenses</TITLE> <LINK type="text/css" rel="stylesheet" href="../static/codelab.css"> <!--MARK-Z--> <SCRIPT> function toggleBlock(heading, whichID) { var image = heading.childNodes[0]; var block = document.getElementById(whichID); if (block) { if (getDisplay(block) == 'block') { block.style.display = 'none'; image.src = 'static/closed.gif'; } else { // "none" or "" block.style.display = 'block'; image.src = 'static/open.gif'; } } } function getDisplay(block) { var value = block.style.display; if (!value) { if (document.defaultView) { var computedStyle = document.defaultView.getComputedStyle(block, ""); value = computedStyle.getPropertyValue('display'); } else if (block.currentStyle) { value = block.currentStyle.display; } } return value; } </SCRIPT> </HEAD> <BODY bgcolor="#ffffff"> <DIV class="banner"></DIV> <H1><FONT size="+2"><IMG src="/static/gruyere-78.png" style="vertical-align:middle"> Web Application Exploits and Defenses (Part 2)</FONT> </H1> <P> A Codelab by Bruce Leban, Mugdha Bendre, and Parisa Tabriz </P> <DIV class="printable"> <DIV class="column1"> Table of Contents <UL> <LI class="L1" style="padding-top:0pt"> <A href="/#0__hackers">Beat the hackers</A></LI> <LI class="L1" style="padding-top:0pt"> <A href="/#0__gruyere">Gruyere</A></LI> <LI class="L1"> <A href="/part1#1__setup">Set-up</A> <UL> <LI class="L2"> <A href="/part1#1__reset_button">Reset Button</A></LI> <LI class="L2"> <A href="/part1#1__about_the_code">About the Code</A></LI> <LI class="L2"> <A href="/part1#1__features_and_technologies">Features and Technologies</A></LI> </UL></LI> <LI class="L1"> <A href="/part1#1__using_gruyere">Using Gruyere</A></LI> <LI class="L1"> <A href="/part2#2__cross_site_scripting">Cross-Site Scripting (XSS)</A> <UL> <LI class="L2"> <A href="/part2#2__xss_challenge">XSS Challenges</A></LI> <LI class="L2"> <A href="/part2#2__file_upload_xss">File Upload XSS</A></LI> <LI class="L2"> <A href="/part2#2__reflected_xss">Reflected XSS</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss">Stored XSS</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss_via_html_attribute">Stored XSS via HTML Attribute</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss_via_ajax">Stored XSS via AJAX</A></LI> <LI class="L2"> <A href="/part2#2__reflected_xss_via_ajax">Reflected XSS via AJAX</A></LI> <LI class="L2"> <A href="/part2#2__more_about_xss">More about XSS</A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__client_state_manipulation">Client-State Manipulation</A> <UL> <LI class="L2"> <A href="/part3#3__elevation_of_privilege"> Elevation of Privilege </A></LI> <LI class="L2"> <A href="/part3#3__cookie_manipulation"> Cookie Manipulation </A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__cross_site_request_forgery">Cross-Site Request Forgery (XSRF)</A> <UL> <LI class="L2"> <A href="/part3#3__xsrf_challenge">XSRF Challenge</A></LI> <LI class="L2"> <A href="/part3#3__more_about_preventing_xsrf">More about preventing XSRF</A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__cross_site_script_inclusion">Cross Site Script Inclusion (XSSI)</A> <UL> <LI class="L2"> <A href="/part3#3__xssi_challenge">XSSI Challenge</A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__path_traversal">Path Traversal</A> <UL> <LI class="L2"> <A href="/part4#4__information_disclosure_path_traversal">Information disclosure via path traversal </A></LI> <LI class="L2"> <A href="/part4#4__data_tampering_path_traversal">Data tampering via path traversal </A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__denial_of_service">Denial of Service</A> <UL> <LI class="L2"> <A href="/part4#4__dos_quit_server">DoS - Quit the Server</A></LI> <LI class="L2"> <A href="/part4#4__dos_overload_server">DoS - Overloading the Server</A></LI> <LI class="L2"> <A href="/part4#4__more_dos">More on Denial of Service</A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__code_execution">Code Execution</A> <UL> <LI class="L2"> <A href="/part4#4__code_execution_challenge">Code Execution Challenge</A></LI> <LI class="L2"> <A href="/part4#4__more_code_execution">More on Remote Code Execution</A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__configuration_vulnerabilities">Configuration Vulnerabilities</A> <UL> <LI class="L2"> <A href="/part5#5__information_disclosure_config_1">Information disclosure #1</A></LI> <LI class="L2"> <A href="/part5#5__information_disclosure_config_2">Information disclosure #2</A></LI> <LI class="L2"> <A href="/part5#5__information_disclosure_bug_3">Information disclosure #3 </A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__ajax_vulnerabilities">AJAX vulnerabilities</A> <UL> <LI class="L2"> <A href="/part5#5__dos_via_ajax">DoS via AJAX</A></LI> <LI class="L2"> <A href="/part5#5__phishing_via_ajax">Phishing via AJAX</A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__other_vulnerabilities"> Other Vulnerabilities </A> </H2> <UL> <LI class="L2"> <A href="/part5#5__buffer_and_integer_overflow">Buffer Overflow and Integer Overflow</A></LI> <LI class="L2"> <A href="/part5#5__sql_injection"> SQL Injection </A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__after_the_codelab">After the Codelab</A></LI> </UL> </DIV> <DIV class="column2"> <P></P> <P></P> <P> <NOAUTOLINK> </NOAUTOLINK></P> <H2><A name="2__cross_site_scripting"> </A> Cross-Site Scripting (XSS) </H2> <P> Cross-site scripting (XSS) is a vulnerability that permits an attacker to inject code (typically HTML or JavaScript) into contents of a website not under the attacker's control. When a victim views such a page, the injected code executes in the victim's browser. Thus, the attacker has bypassed the browser's <A href="https://www.google.com/search?q=same+origin+policy">same origin policy</A> and can steal victim's private information associated with the website in question. </P> <P> In a <b>reflected XSS</b> attack, the attack is in the request itself (frequently the URL) and the vulnerability occurs when the server inserts the attack in the response verbatim or incorrectly escaped or sanitized. The victim triggers the attack by browsing to a malicious URL created by the attacker. In a <b>stored XSS</b> attack, the attacker stores the attack in the application (e.g., in a snippet) and the victim triggers the attack by browsing to a page on the server that renders the attack, by not properly escaping or sanitizing the stored data. </P> <P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_desc');"><IMG src="/static/closed.gif"> More details </H4> <DIV id="xss_desc" style="display:none"> <P></P> <P> To understand how this could happen: suppose the URL <CODE>https://www.google.com/search?q=flowers</CODE> returns a page containing the HTML fragment </P> <P></P> <PRE> <p>Your search for 'flowers' returned the following results:</p> </PRE> <P> that is, the value of the query parameter <CODE>q</CODE> is inserted verbatim into the page returned by Google. If <CODE>www.google.com</CODE> did not do any validation or escaping of <CODE>q</CODE> (it does), an attacker could craft a link that looks like this:<BR> <PRE> https://www.google.com/search?q=flowers+%3Cscript%3Eevil_script()%3C/script%3E </PRE> and trick a victim into clicking on this link. When a victim loads this link, the following page gets rendered in the victim's browser: </P> <P></P> <PRE> <p>Your search for 'flowers<script>evil_script()</script>' returned the following results:</p> </PRE> <P> And the browser executes <CODE>evil_script()</CODE>. And since the page comes from <CODE>www.google.com</CODE>, <CODE>evil_script()</CODE> is executed in the context of <CODE>www.google.com</CODE> and has access to all the victim's browser state and cookies for that domain. </P> <P> Note that the victim does not even need to explicitly click on the malicious link. Suppose the attacker owns <CODE>www.evil.example.com</CODE>, and creates a page with an <CODE><iframe></CODE> pointing to the malicious link; if the victim visits <CODE>www.evil.example.com</CODE>, the attack will silently be activated. </P> </DIV> </DIV> <P></P> <H3><A name="2__xss_challenge"> </A> XSS Challenges </H3> <P> Typically, if you can get JavaScript to execute on a page when it's viewed by another user, you have an XSS vulnerability. A simple JavaScript function to use when hacking is the <CODE>alert()</CODE> function, which creates a pop-up box with whatever string you pass as an argument. </P> <P>You might think that inserting an alert message isn't terribly dangerous, but if you can inject that, you can inject other scripts that are more malicious. It is not necessary to be able to inject any particular special character in order to attack. If you can inject <CODE>alert(1)</CODE> then you can inject arbitrary script using <CODE>eval(String.fromCharCode(...))</CODE>. </P> <P> Your challenge is to find XSS vulnerabilities in Gruyere. You should look for vulnerabilities both in URLs and in stored data. Since XSS vulnerabilities usually involve applications not properly handling untrusted user data, a common method of attack is to enter random text in input fields and look at how it gets rendered in the response page's HTML source. But before we do that, let's try something simpler. </P> <P></P> <H3><A name="2__file_upload_xss"> </A> File Upload XSS </H3> <P> <IMG src="/static/cheese_b.png" style="vertical-align:middle; padding-right: 5px;"> <B>Can you upload a file that allows you to execute arbitrary script on the <CODE>google-gruyere.appspot.com</CODE> domain?</B> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'upload_xss_hint1');"><IMG src="/static/closed.gif"> Hint </H4> <DIV id="upload_xss_hint1" style="display:none"> <P> You can upload HTML files and HTML files can contain script. </P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'upload_xss_sol');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="upload_xss_sol" style="display:none"> <P><B>To exploit,</B> upload a <CODE>.html</CODE> file containing a script like this: <PRE> <script> alert(document.cookie); </script> </PRE> </P><P> <B>To fix,</B> host the content on a separate domain so the script won't have access to any content from your domain. That is, instead of hosting user content on <CODE>example.com/<I>username</I></CODE> we would host it at <CODE><I>username</I>.usercontent.example.com</CODE> or <CODE><I>username</I>.example-usercontent.com</CODE>. (Including something like "<CODE>usercontent</CODE>" in the domain name avoids attackers registering usernames that look innocent like <CODE>wwww</CODE> and using them for phishing attacks.) <P> </DIV> </DIV> <H3><A name="2__reflected_xss"> </A> Reflected XSS </H3> <P> There's an interesting problem here. Some browsers have built-in protection against reflected XSS attacks. There are also browser extensions like NoScript that provide some protection. If you're using one of those browsers or extensions, you may need to use a different browser or temporarily disable the extension to execute these attacks. </P> <P>At the time this codelab was written, the two browsers which had this protection were IE and Chrome. To work around this, Gruyere automatically includes a <TT>X-XSS-Protection: 0</TT> HTTP header in every response which is recognized by IE and will be recognized by future versions of Chrome. (It's available in the developer channel now.) If you're using Chrome, you can try starting it with the <TT>--disable-xss-auditor</TT> flag by entering one of these commands: <UL><LI>Windows: <TT>"C:\Documents and Settings\USERNAME\Local Settings\Application Data\Google\Chrome\Application\chrome.exe" --disable-xss-auditor</TT> <LI>Mac: <TT>/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --disable-xss-auditor</TT> <LI>GNU/Linux: <TT>/opt/google/chrome/google-chrome --disable-xss-auditor</TT> </UL> If you're using Firefox with the NoScript extension, add <TT>google-gruyere.appspot.com</TT> to the allow list. If you still can't get the XSS attacks to work, try a different browser. </P> <P>You may think that you don't need to worry about XSS if the browser protects against it. The truth is that the browser protection can't be perfect because it doesn't really know your application and therefore there may be ways for a clever hacker to circumvent that protection. The real protection is to not have an XSS vulnerability in your application in the first place. </P> <IMG src="/static/cheese_b.png" style="vertical-align:middle; padding-right: 5px;"> <B>Find a reflected XSS attack. What we want is a URL that when clicked on will execute a script.</B> <P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_url_hint1');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="xss_url_hint1" style="display:none"> <P> What does this URL do? </P> <P></P> <PRE> https://google-gruyere.appspot.com/382665580745386307547168512335551204731/invalid </PRE> <P></P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_url_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="xss_url_hint2" style="display:none"> <P> The most dangerous characters in a URL are <CODE><</CODE> and <CODE>></CODE>. If you can get an application to directly insert what you want in a page and can get those characters through, then you can probably get a script through. Try these: </P> <P></P> <PRE> https://google-gruyere.appspot.com/382665580745386307547168512335551204731/%3e%3c https://google-gruyere.appspot.com/382665580745386307547168512335551204731/%253e%253c https://google-gruyere.appspot.com/382665580745386307547168512335551204731/%c0%be%c0%bc https://google-gruyere.appspot.com/382665580745386307547168512335551204731/%26gt;%26lt; https://google-gruyere.appspot.com/382665580745386307547168512335551204731/%26amp;gt;%26amp;lt; https://google-gruyere.appspot.com/382665580745386307547168512335551204731/\074\x3c\u003c\x3C\u003C\X3C\U003C https://google-gruyere.appspot.com/382665580745386307547168512335551204731/+ADw-+AD4- </PRE> <P> This tries <CODE>></CODE> and <CODE><</CODE> in many different ways that might be able to make it through the URL and get rendered incorrectly using: verbatim (URL %-encoding), double %-encoding, bad UTF-8 encoding, HTML &-encoding, double &-encoding, and several different variations on C-style encoding. View the resulting source and see if any of those work. (Note: literally typing <CODE>><</CODE> in the URL is identical to <CODE>%3e%3c</CODE> because the browser automatically %-encodes those character. If you are trying to want a literal <CODE>></CODE> or <CODE><</CODE> then you will need to use a tool like curl to send those characters in URL.) </P> <P></P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_url_sol');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="xss_url_sol" style="display:none"> <P> <B>To exploit,</B> create a URL like the following and get a victim to click on it: </P> <P></P> <PRE> https://google-gruyere.appspot.com/382665580745386307547168512335551204731/<script>alert(1)</script> </PRE> <P> <B>To fix,</B> you need to escape user input that is displayed in error messages. Error messages are displayed using <CODE><A href="/code/?resources/error.gtl">error.gtl</A></CODE>, but are not escaped in the template. The part of the template that renders the message is <CODE>{{message}}</CODE> and it's missing the modifier that tells it to escape user input. Add the <CODE>:text</CODE> modifier to escape the user input: </P><PRE> <div class="message">{{_message:text}}</div> </PRE> <P> This flaw would have been best mitigated by a design that escapes all output by default and only displays raw HTML when explicitly tagged to do so. There are also <A href="https://www.google.com/search?q=XSS+auto+escaping" target="_top">autoescaping</A> features available in many template systems. </P> <!--MARK-2--> <P></P> </DIV> </DIV> <P></P> <H3><A name="2__stored_xss"> </A> Stored XSS </H3> <P> <IMG src="/static/cheese_b.png" style="vertical-align:middle; padding-right: 5px;"> <B>Now find a stored XSS. What we want to do is put a script in a place where Gruyere will serve it back to another user.</B> </P> The most obvious place that Gruyere serves back user-provided data is in a snippet (ignoring uploaded files which we've already discussed.)<P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_stored_hint1');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="xss_stored_hint1" style="display:none"> <P> Put this in a snippet and see what you get: </P> <P></P> <PRE> <script>alert(1)</script> </PRE> <P> There are many different ways that script can be embedded in a document. </P> <P></P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_stored_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="xss_stored_hint2" style="display:none"> <P> Hackers don't limit themselves to valid HTML syntax. Try some invalid HTML and see what you get. You may need to experiment a bit in order to find something that will work. There are multiple ways to do this. </P> <P></P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_stored_sol');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="xss_stored_sol" style="display:none"> <P> <B>To exploit,</B> enter any of these as your snippet (there are certainly more methods): </P> <P></P> <PRE> (1) <a onmouseover="alert(1)" href="#">read this!</a> (2) <p <script>alert(1)</script>hello (3) </td <script>alert(1)</script>hello </PRE> <P> Notice that there are multiple failures in sanitizing the HTML. Snippet 1 worked because <CODE>onmouseover</CODE> was inadvertently omitted from the list of disallowed attributes in <CODE><A href="/code/?sanitize.py">sanitize.py</A></CODE>. Snippets 2 and 3 work because browsers tend to be forgiving with HTML syntax and the handling of both start and end tags is buggy. </P> <P> <B>To fix,</B> we need to investigate and fix the sanitizing performed on the snippets. Snippets are sanitized in <CODE>_SanitizeTag</CODE> in the <CODE><A href="/code/?sanitize.py">sanitize.py</A></CODE> file. Let's block snippet 1 by adding <CODE>"onmouseover"</CODE> to the list of <CODE>disallowed_attributes</CODE>. </P> <P> <B>Oops!</B> This doesn't completely solve the problem. Looking at the code that was just fixed, can you find a way to bypass the fix? </P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_stored_not_fixed_hint');"><IMG src="/static/closed.gif"> Hint </H4> <DIV id="xss_stored_not_fixed_hint" style="display:none"> <P> Take a close look at the code in <CODE>_SanitizeTag</CODE> that determines whether or not an HTML attribute is allowed or not. </P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_stored_not_fixed');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="xss_stored_not_fixed" style="display:none"> <P> The fix was insufficient because the code that checks for disallowed attributes is case sensitive and HTML is not. So this still works: </P> <P></P> <PRE> (1') <a ONMOUSEOVER="alert(1)" href="#">read this!</a> </PRE> <P> Correctly sanitizing HTML is a tricky problem. The <CODE>_SanitizeTag</CODE> function has a number of critical design flaws: </P> <P></P> <UL> <LI> It does not validate the well-formedness of the input HTML. As we see, badly formed HTML passes through the sanitizer unchanged. Since browsers typically apply very lenient parsing, it is very hard to predict the browser's interpretation of the given HTML unless we exercise strict control on its format. </LI> <LI> It uses blacklisting of attributes, which is a bad technique. One of our exploits got past the blacklist simply by using an uppercase version of the attribute. There could be other attributes <A href="https://www.w3.org/TR/html40/index/attributes.html" target="_top">missing from this list</A> that are dangerous. It is always better to whitelist known good values. </LI> <LI> The sanitizer does not do any further sanitization of attribute values. This is dangerous since URI attributes like <CODE>href</CODE> and <CODE>src</CODE> and the <CODE>style</CODE> attribute can all be used to inject JavaScript. </LI> </UL> <P> The right approach to HTML sanitization is to: </P><UL> <LI> Parse the input into an intermediate DOM structure, then rebuild the body as well-formed output. </LI> <LI> Use strict whitelists for allowed tags and attributes. </LI> <LI> Apply strict sanitization of URL and CSS attributes if they are permitted. </LI> </UL> <P>Whenever possible it is preferable to use an already available known and proven <A href="https://www.google.com/search?q=sanitize+html" target="_top">HTML sanitizer</A>. </P> <!--MARK-3--> <P></P> </DIV> </DIV> </DIV> <P></P> <H3><A name="2__stored_xss_via_html_attribute"> </A> Stored XSS via HTML Attribute </H3> <P> <IMG src="/static/cheese_b.png" style="vertical-align:middle; padding-right: 5px;"> <B>You can also do XSS by injecting a value into an HTML attribute. Inject a script by setting the color value in a profile.</B> </P><DIV style="margin-left:18pt"> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_color_hint');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="xss_color_hint" style="display:none"> <P> The color is rendered as <CODE>style='color:<I>color</I>'</CODE>. Try including a single quote character in your color name. </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_color_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="xss_color_hint2" style="display:none"> <P> You can insert an HTML attribute that executes a script. </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_color_sol');"><IMG src="/static/closed.gif"> Exploit and Fixes </H4> <DIV id="xss_color_sol" style="display:none"> <P> <B>To exploit,</B> use the following for your color preference: </P> <P></P> <PRE> red' onload='alert(1)' onmouseover='alert(2) </PRE> <P> You may need to move the mouse over the snippet to trigger the attack. This attack works because the first quote ends the <CODE>style</CODE> attribute and the second quote starts the onload attribute. </P> <P> But this attack shouldn't work at all. Take a look at <CODE><A href="/code/?resources/home.gtl">home.gtl</A></CODE> where it renders the color. It says <CODE>style='{{color:text}}'</CODE> and as we saw earlier, the <CODE>:text</CODE> part tells it to escape text. So why doesn't this get escaped? In <CODE><A href="/code/?gtl.py">gtl.py</A></CODE>, it calls <CODE>cgi.escape(str(value))</CODE> which takes an optional second parameter that indicates that the value is being used in an HTML attribute. So you can replace this with <CODE>cgi.escape(str(value),True)</CODE>. Except that doesn't fix it! The problem is that <CODE>cgi.escape</CODE> assumes your HTML attributes are enclosed in double quotes and this file is using single quotes. (This should teach you to always carefully read the documentation for libraries you use and to always test that they do what you want.) </P> <P> You'll note that this attack uses both <CODE>onload</CODE> and <CODE>onmouseover</CODE>. That's because even though W3C specifies that onload events is only supported on <CODE>body</CODE> and <CODE>frameset</CODE> elements, some browsers support them on other elements. So if the victim is using one of those browsers, the attack always succeeds. Otherwise, it succeeds when the user moves the mouse. It's not uncommon for attackers to use multiple attack vectors at the same time. </P> <P> <B>To fix,</B> we need to use a correct text escaper, that escapes single and double quotes too. Add the following function to <CODE><A href="/code/?gtl.py">gtl.py</A></CODE> and call it instead of <CODE>cgi.escape</CODE> for the <CODE>text</CODE> escaper. </P> <P></P> <PRE> def _EscapeTextToHtml(var): """Escape HTML metacharacters. This function escapes characters that are dangerous to insert into HTML. It prevents XSS via quotes or script injected in attribute values. It is safer than cgi.escape, which escapes only <, >, & by default. cgi.escape can be told to escape double quotes, but it will never escape single quotes. """ meta_chars = { '"': '&quot;', '\'': '&#39;', # Not &apos; '&': '&amp;', '<': '&lt;', '>': '&gt;', } escaped_var = "" for i in var: if i in meta_chars: escaped_var = escaped_var + meta_chars[i] else: escaped_var = escaped_var + i return escaped_var </PRE> <P> <B>Oops!</B> This doesn't completely solve the problem. Even with the above fix in place, the color value is still vulnerable. <H4 style="cursor: pointer" onclick="toggleBlock(this, 'another_style_xss_hint1');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="another_style_xss_hint1" style="display:none"> <P> Some browsers allow you to include script in stylesheets. </P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'another_style_xss_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="another_style_xss_hint2" style="display:none"> <P> The easiest browser to exploit in this way is Internet Explorer which supports dynamic CSS properties. </P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'another_style_xss_sol');"><IMG src="/static/closed.gif"> Another Exploit and Fix </H4> <DIV id="another_style_xss_sol" style="display:none"> <P> Internet Explorer's dynamic CSS properites (aka CSS expressions) make this attack particularly easy. </P> <B>To exploit,</B> use the following for your color preference: <PRE> expression(alert(1)) </PRE> <P> While other browsers don't support CSS expressions, there are other dangerous CSS properties, such as Mozilla's <CODE>-moz-binding</CODE>. </P> <P> <B>To fix,</B> we need to sanitize the color as a color. The best thing to do would be to add a new output sanitizing form to gtl, i.e., we would write <CODE>{{foo:color}}</CODE> which makes sure <CODE>foo</CODE> is safe to use as a color. This function can be used to sanitize: </P> <PRE> SAFE_COLOR_RE = re.compile(r"^#?[a-zA-Z0-9]*$") def _SanitizeColor(color): """Sanitizes a color, returning 'invalid' if it's invalid. A valid value is either the name of a color or # followed by the hex code for a color (like #FEFFFF). Returning an invalid value value allows a style sheet to specify a default value by writing 'color:default; color:{{foo:color}}'. """ if SAFE_COLOR_RE.match(color): return color return 'invalid' </PRE> <P> Colors aren't the only values we might want to allow users to provide. You should do similar sanitizing for user-provided fonts, sizes, urls, etc. It's helpful to do input validation, so that when a user enters an invalid value, you'll reject it at that time. But only doing input validation would be a mistake: if you find an error in your validation code or a new browser exposes a new attack vector, you'd have to go back and scrub all previously entered values. Or, you could add the output validation which you should have been doing in the first place. </DIV> </P></DIV> </DIV> <P></P> <H3><A name="2__stored_xss_via_ajax"> </A> Stored XSS via AJAX </H3> <P> <IMG src="/static/cheese_b.png" style="vertical-align:middle; padding-right: 5px;"> <B>Find an XSS attack that uses a bug in Gruyere's AJAX code.</B> The attack should be triggered when you click the refresh link on the page. </P> <P></P> <DIV style="margin-left:18pt"> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_ajax_hint');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="xss_ajax_hint" style="display:none"> <P> Run <CODE>curl</CODE> on <CODE>https://google-gruyere.appspot.com/382665580745386307547168512335551204731/feed.gtl</CODE> and look at the result. (Or browse to it in your browser and view source.) You'll see that it includes each user's first snippet into the response. This entire response is then evaluated on the client side which then inserts the snippets into the document. Can you put something in your snippet that will be parsed differently than expected? </P> <P></P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_ajax_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="xss_ajax_hint2" style="display:none"> <P> Try putting some quotes (<CODE>"</CODE>) in your snippet. </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_ajax_sol');"><IMG src="/static/closed.gif"> Exploit and Fixes </H4> <DIV id="xss_ajax_sol" style="display:none"> <P> <B>To exploit,</B> Put this in your snippet: </P> <P></P> <PRE> all <span style=display:none>" + (alert(1),"") + "</span>your base </PRE> <P> The JSON should look like <PRE>_feed(({..., "Mallory": "snippet", ...}))</PRE> but instead looks like this: <PRE>_feed({..., "Mallory": <U>"all <span style=display:none>"</U> + <U>(alert(1),"")</U> + <U>"</span>your base"</U>, ...})</PRE> Each underlined part is a separate expression. Note that this exploit is written to be invisible both in the original page rendering (because of the <CODE><span style=display:none></CODE>) and after refresh (because it inserts only an empty string). All that will appear on the screen is <A href="https://www.google.com/search?q=all+your+base+are+belong+to+us" target="_top">all your base</A>. There are bugs on both the server and client sides which enable this attack. </P> <P> <B>To fix,</B> first, on the server side, the text is incorrectly escaped when it is rendered in the JSON response. The template says <CODE>{{snippet.0:html}}</CODE> but that's not enough. This text is going to be inserted into the innerHTML of a DOM node so the HTML does have to be sanitized. However, that sanitized text is then going to be inserted into JavaScript and single and double quotes have to be escaped. That is, adding support for <CODE>{{...:js}}</CODE> to GTL would not be sufficient; we would also need to support something like <CODE>{{...:html:js}}</CODE>. </P><P>To escape quotes, use <CODE>\x27</CODE> and <CODE>\x22</CODE> for single and double quote respectively. Replacing them with <CODE>&#27;</CODE> and <CODE>&quot;</CODE> is incorrect as those are not recognized in JavaScript strings and will break quotes around HTML attribute. </P> <P> Second, in the browser, Gruyere converts the JSON by using JavaScript's <CODE>eval</CODE>. In general, <CODE>eval</CODE> is very dangerous and should rarely be used. If it used, it must be used very carefully, which is hardly the case here. We should be using the JSON parser which ensures that the string does not include any unsafe content. The JSON parser is available at <A href="http://www.json.org/" target="_top">json.org</A>. </P> <P></P> </DIV> </DIV> <P></P> <H3><A name="2__reflected_xss_via_ajax"> </A> Reflected XSS via AJAX </H3> <P> <IMG src="/static/cheese_b.png" style="vertical-align:middle; padding-right: 5px;"><B>Find a URL that when clicked on will execute a script using one of Gruyere's AJAX features.</B> </P> <P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_ajax2_hint');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="xss_ajax2_hint" style="display:none"> <P> When Gruyere refreshes a user snippets page, it uses <PRE>https://google-gruyere.appspot.com/382665580745386307547168512335551204731/feed.gtl?uid=value</PRE> and the result is the script <PRE>_feed((["user", "snippet1", ... ]))</PRE> </P> <P></P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_ajax2_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="xss_ajax2_hint2" style="display:none"> <P> This uses a different vulnerability, but the exploit is very similar to the previous reflected XSS exploit. </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_ajax2_sol');"><IMG src="/static/closed.gif"> Exploit and Fixes </H4> <DIV id="xss_ajax2_sol" style="display:none"> <P> <B>To exploit,</B> create a URL like the following and get a victim to click on it: </P> <P></P> <PRE> https://google-gruyere.appspot.com/382665580745386307547168512335551204731/feed.gtl?uid=<script>alert(1)</script> https://google-gruyere.appspot.com/382665580745386307547168512335551204731/feed.gtl?uid=%3Cscript%3Ealert(1)%3C/script%3E </PRE> <P> This renders as <PRE>_feed((["<script>alert(1)</script>"]))</PRE> which surprisingly <I>does</I> execute the script. The bug is that Gruyere returns all gtl files as content type <CODE>text/html</CODE> and browsers are very tolerant of what HTML files they accept. </P> <P> <B>To fix,</B> you need to make sure that your JSON content can never be interpreted as HTML. Even though literal <CODE><</CODE> and <CODE>></CODE> are allowed in JavaScript strings, you need to make sure they don't appear literally where a browser can misinterpret them. Thus, you'd need to modify <CODE>{{...:js}}</CODE> to replace them with the JavaScript escapes <CODE>\x3c</CODE> and <CODE>\x3e</CODE>. It is always safe to write <CODE>'\x3c\x3e'</CODE> in Javscript strings instead of <CODE>'<>'</CODE>. (And, as noted above, using the HTML escapes <CODE>&lt;</CODE> and <CODE>&gt;</CODE> is incorrect.) </P> <P> You should also always set the content type of your responses, in this case serving JSON results as <CODE>application/javascript.</CODE> This alone doesn't solve the problem because browsers don't always respect the content type: browsers sometimes do "sniffing" to try to "fix" results from servers that don't provide the correct content type. </P> <P><B>But wait, there's more!</B> Gruyere doesn't set the content encoding either. And some browsers try to guess what the encoding type of a document is or an attacker may be able to embed content in a document that defines the content type. So, for example, if an attacker can trick the browser into thinking a document is <CODE><A href="https://www.google.com/search?q=utf-7">UTF-7</A></CODE> then it could embed a script tag as <CODE>+ADw-script+AD4-</CODE> since <CODE>+ADw-</CODE> and <CODE>+AD4-</CODE> are alternate encodings for <CODE><</CODE> and <CODE>></CODE>. So always set both the content type <I>and</I> the content encoding of your responses, e.g., for HTML:</P> <PRE> Content-Type: text/html; charset=utf-8 </PRE> </DIV> </DIV> <H3><A name="2__more_about_xss"> </A> More about XSS </H3> <P> In addition to the XSS attacks described above, there are quite a few more ways to attack Gruyere with XSS. Collect them all! </P> <P> XSS is a difficult beast. On one hand, a fix to an XSS vulnerability is usually trivial and involves applying the correct sanitizing function to user input when it's displayed in a certain context. On the other hand, if history is any indication, this is extremely difficult to get right. <A href="https://www.kb.cert.org/vuls/" target="_top">US-CERT</A> reports dozens of publicly disclosed XSS vulnerabilities involving multiple companies. </P> <P> Though there is no magic defense to getting rid of XSS vulnerabilities, here are some steps you should take to prevent these types of bugs from popping up in your products: </P> <P></P> <OL> <LI> First, make sure you <A href="https://www.google.com/search?q=understanding+cross-site+scripting" target="_top">understand the problem</A>. </LI> <LI> Wherever possible, do sanitizing via templates features instead of calling escaping functions in source code. This way, all of your escaping is done in one place and your product can benefit from security technologies designed for template systems that verify their correctness or actually do the escaping for you. Also, familiarize yourself with the other security features of your template system. </LI> <LI> Employ good testing practices with respect to XSS. </LI> <LI> Don't write your own template library :) </LI> </OL> <!--MARK-6--> <BR><P></P> <FONT SIZE="+2"> <A href="/part3">Continue >></A> </FONT><BR> <BR> <P style="font-size:x-small"> © Google 2017 <A href="https://www.google.com/intl/en/policies/terms/">Terms of Service</A> <BR>The code portions of this codelab are licensed under the Creative Commons Attribution-No Derivative Works 3.0 United States license <<A href="https://creativecommons.org/licenses/by-nd/3.0/us/">https://creativecommons.org/licenses/by-nd/3.0/us</A>>. Brief excerpts of the code may be used for educational or instructional purposes provided this notice is kept intact. Except as otherwise noted the remainder of this codelab is licensed under the Creative Commons Attribution 3.0 United States license <<A href="https://creativecommons.org/licenses/by/3.0/us/">https://creativecommons.org/licenses/by/3.0/us</A>>. </P> </BODY></HTML> |
| URL | http://google-gruyere.appspot.com/part3 |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 363 bytes. |
GET http://google-gruyere.appspot.com/part3 HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/ Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 244 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: f16a707de42dff20c808e8c075a2e977 Date: Tue, 06 Feb 2024 17:42:21 GMT Server: Google Frontend Content-Length: 29253 |
| Response Body - size: 29,253 bytes. |
<HTML><HEAD><META http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<TITLE>Web Application Exploits and Defenses</TITLE> <LINK type="text/css" rel="stylesheet" href="../static/codelab.css"> <!--MARK-Z--> <SCRIPT> function toggleBlock(heading, whichID) { var image = heading.childNodes[0]; var block = document.getElementById(whichID); if (block) { if (getDisplay(block) == 'block') { block.style.display = 'none'; image.src = 'static/closed.gif'; } else { // "none" or "" block.style.display = 'block'; image.src = 'static/open.gif'; } } } function getDisplay(block) { var value = block.style.display; if (!value) { if (document.defaultView) { var computedStyle = document.defaultView.getComputedStyle(block, ""); value = computedStyle.getPropertyValue('display'); } else if (block.currentStyle) { value = block.currentStyle.display; } } return value; } </SCRIPT> </HEAD> <BODY bgcolor="#ffffff"> <DIV class="banner"></DIV> <H1><FONT size="+2"><IMG src="/static/gruyere-78.png" style="vertical-align:middle"> Web Application Exploits and Defenses (Part 3)</FONT> </H1> <P> A Codelab by Bruce Leban, Mugdha Bendre, and Parisa Tabriz </P> <DIV class="printable"> <DIV class="column1"> Table of Contents <UL> <LI class="L1" style="padding-top:0pt"> <A href="/#0__hackers">Beat the hackers</A></LI> <LI class="L1" style="padding-top:0pt"> <A href="/#0__gruyere">Gruyere</A></LI> <LI class="L1"> <A href="/part1#1__setup">Set-up</A> <UL> <LI class="L2"> <A href="/part1#1__reset_button">Reset Button</A></LI> <LI class="L2"> <A href="/part1#1__about_the_code">About the Code</A></LI> <LI class="L2"> <A href="/part1#1__features_and_technologies">Features and Technologies</A></LI> </UL></LI> <LI class="L1"> <A href="/part1#1__using_gruyere">Using Gruyere</A></LI> <LI class="L1"> <A href="/part2#2__cross_site_scripting">Cross-Site Scripting (XSS)</A> <UL> <LI class="L2"> <A href="/part2#2__xss_challenge">XSS Challenges</A></LI> <LI class="L2"> <A href="/part2#2__file_upload_xss">File Upload XSS</A></LI> <LI class="L2"> <A href="/part2#2__reflected_xss">Reflected XSS</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss">Stored XSS</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss_via_html_attribute">Stored XSS via HTML Attribute</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss_via_ajax">Stored XSS via AJAX</A></LI> <LI class="L2"> <A href="/part2#2__reflected_xss_via_ajax">Reflected XSS via AJAX</A></LI> <LI class="L2"> <A href="/part2#2__more_about_xss">More about XSS</A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__client_state_manipulation">Client-State Manipulation</A> <UL> <LI class="L2"> <A href="/part3#3__elevation_of_privilege"> Elevation of Privilege </A></LI> <LI class="L2"> <A href="/part3#3__cookie_manipulation"> Cookie Manipulation </A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__cross_site_request_forgery">Cross-Site Request Forgery (XSRF)</A> <UL> <LI class="L2"> <A href="/part3#3__xsrf_challenge">XSRF Challenge</A></LI> <LI class="L2"> <A href="/part3#3__more_about_preventing_xsrf">More about preventing XSRF</A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__cross_site_script_inclusion">Cross Site Script Inclusion (XSSI)</A> <UL> <LI class="L2"> <A href="/part3#3__xssi_challenge">XSSI Challenge</A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__path_traversal">Path Traversal</A> <UL> <LI class="L2"> <A href="/part4#4__information_disclosure_path_traversal">Information disclosure via path traversal </A></LI> <LI class="L2"> <A href="/part4#4__data_tampering_path_traversal">Data tampering via path traversal </A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__denial_of_service">Denial of Service</A> <UL> <LI class="L2"> <A href="/part4#4__dos_quit_server">DoS - Quit the Server</A></LI> <LI class="L2"> <A href="/part4#4__dos_overload_server">DoS - Overloading the Server</A></LI> <LI class="L2"> <A href="/part4#4__more_dos">More on Denial of Service</A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__code_execution">Code Execution</A> <UL> <LI class="L2"> <A href="/part4#4__code_execution_challenge">Code Execution Challenge</A></LI> <LI class="L2"> <A href="/part4#4__more_code_execution">More on Remote Code Execution</A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__configuration_vulnerabilities">Configuration Vulnerabilities</A> <UL> <LI class="L2"> <A href="/part5#5__information_disclosure_config_1">Information disclosure #1</A></LI> <LI class="L2"> <A href="/part5#5__information_disclosure_config_2">Information disclosure #2</A></LI> <LI class="L2"> <A href="/part5#5__information_disclosure_bug_3">Information disclosure #3 </A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__ajax_vulnerabilities">AJAX vulnerabilities</A> <UL> <LI class="L2"> <A href="/part5#5__dos_via_ajax">DoS via AJAX</A></LI> <LI class="L2"> <A href="/part5#5__phishing_via_ajax">Phishing via AJAX</A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__other_vulnerabilities"> Other Vulnerabilities </A> </H2> <UL> <LI class="L2"> <A href="/part5#5__buffer_and_integer_overflow">Buffer Overflow and Integer Overflow</A></LI> <LI class="L2"> <A href="/part5#5__sql_injection"> SQL Injection </A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__after_the_codelab">After the Codelab</A></LI> </UL> </DIV> <DIV class="column2"> <P></P> <P></P> <P> <NOAUTOLINK> </NOAUTOLINK></P> <H2><A name="3__client_state_manipulation"> </A> Client-State Manipulation </H2> <P> When a user interacts with a web application, they do it indirectly through a browser. When the user clicks a button or submits a form, the browser sends a request back to the web server. Because the browser runs on a machine that can be controlled by an attacker, the application must not trust any data sent by the browser. </P><P> It might seem that not trusting any user data would make it impossible to write a web application but that's not the case. If the user submits a form that says they wish to purchase an item, it's OK to trust that data. But if the submitted form also includes the price of the item, that's something that cannot be trusted. </P> <P></P> <H3><A name="3__elevation_of_privilege"> </A> Elevation of Privilege </H3> <IMG src="/static/cheese_w.png" style="vertical-align:middle; padding-right: 5px;"> <B>Convert your account to an administrator account.</B> <P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'elevation_hint');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="elevation_hint" style="display:none"> <P> Take a look at the <CODE><A href="/code/?resources/editprofile.gtl">editprofile.gtl</A></CODE> page that users and administrators use to edit profile settings. If you're not an administrator, the page looks a bit different. Can you figure out how to fool Gruyere into letting you use this page to update your account? </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'elevation_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="elevation_hint2" style="display:none"> <P> Can you figure out how to fool Gruyere into <i>thinking</i> you used this page to update your account? </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'clientstate');"><IMG src="/static/closed.gif"> Exploit and Fixes </H4> <DIV id="clientstate" style="display:none"> <P> You can convert your account to being an administrator by issuing either of the following requests: </P><UL> <LI> <CODE>https://google-gruyere.appspot.com/382665580745386307547168512335551204731/saveprofile?action=update&is_admin=True</CODE> </LI> <LI> <CODE>https://google-gruyere.appspot.com/382665580745386307547168512335551204731/saveprofile?action=update&is_admin=True&uid=username</CODE> (which will make any <CODE>username</CODE> into an an admin) </LI> </UL> <P>After visiting this URL, your account is now marked as an administrator but your cookie still says you're not. So sign out and back in to get a new cookie. After logging in, notice the 'Manage this server' link on the top right.</P> <P>The bug here is that there is no validation on the server side that the request is authorized. The only part of the code that restricts the changes that a user is allowed to make are in the template, hiding parts of the UI that they shouldn't have access to. The correct thing to do is to check for authorization on the server, at the time that the request is received. </P> </DIV> </DIV> <P></P> <H3><A name="3__cookie_manipulation"> </A> Cookie Manipulation </H3> Because the HTTP protocol is stateless, there's no way a web server can automatically know that two requests are from the same user. For this reason, <A href="https://www.google.com/search?q=http+cookies">cookies</a> were invented. When a web site includes a cookie (an arbitrary string) in a HTTP response, the browser automatically sends the cookie back to the browser on the next request. Web sites can use the cookie to save session state. Gruyere uses cookies to remember the identity of the logged in user. Since the cookie is stored on the client side, it's vulnerable to manipulation. Gruyere protects the cookies from manipulation by adding a hash to it. Notwithstanding the fact that this hash isn't very good protection, you don't need to break the hash to execute an attack. </P><P> <IMG src="/static/cheese_bw.png" style="vertical-align:middle; padding-right: 5px;"> <B>Get Gruyere to issue you a cookie for someone else's account.</B> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'cookie_hint');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="cookie_hint" style="display:none"> <P> You don't need to look at the Gruyere cookie parsing code. You just need to know what the cookies look like. Gruyere's cookies use the format: <PRE> <i>hash</i>|<i>username</i>|admin|author </PRE> </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'cookie_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="cookie_hint2" style="display:none"> <P> Gruyere issues a cookie when you log in. Can you trick it into issuing you a cookie that looks like another user's cookie? </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'cookieparsing');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="cookieparsing" style="display:none"> <P> You can get Gruyere to issue you a cookie for someone else's account by creating a new account with username <CODE>"foo|admin|author"</CODE>. When you log into this account, it will issue you the cookie <CODE>"hash|foo|admin|author||author"</CODE> which actually logs you into <CODE>foo</CODE> as an administrator. (So this is also an elevation of privilege attack.) </P> <P>Having no restrictions on the characters allowed in usernames means that we have to be careful when we handle them. In this case, the cookie parsing code is tolerant of malformed cookies and it shouldn't be. It should escape the username when it constructs the cookie and it should reject a cookie if it doesn't match the exact pattern it is expecting. </P> <P>Even if we fix this, Python's hash function is not cryptographically secure. If you look at Python's <CODE>string_hash</CODE> function in <CODE><a href="https://svn.python.org/projects/python/trunk/Objects/stringobject.c">python/Objects/stringobject.cc</A></CODE> you'll see that it hashes the string strictly from left to right. That means that we don't need to know the cookie secret to generate our own hashes; all we need is another string that hashes to the same value, which we can find in a relatively short time on a typical PC. In contrast, with a cryptographic hash function, changing any bit of the string will change many bits of the hash value in an unpredictable way. At a minimum, you should use a secure hash function to protect your cookies. You should also consider encrypting the entire cookie as plain text cookies can expose information you might not want exposed. </P> <P>And these cookies are also vulnerable to a replay attack. Once a user is issued a cookie, it's good forever and there's no way to revoke it. So if a user is an administrator at one time, they can save the cookie and continue to act as an administrator even if their administrative rights are taken away. While it's convenient to not have to make a database query in order to check whether or not a user is an administrator, that might be too dangerous a detail to store in the cookie. If avoiding additional database access is important, the server could cache a list of recent admin users. Including a timestamp in a cookie and expiring it after some period of time also mitigates against a replay attack.</P> <P><B>Another challenge:</B> Since account names are limited to 16 characters, it seems that this trick would not work to log in to the actual <CODE>administrator</CODE> account since <CODE>"administrator|admin"</CODE> is 19 characters. Can you figure out how to bypass that restriction? </P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'cookie2');"><IMG src="/static/closed.gif"> Additional Exploit and Fix </H4> <DIV id="cookie2" style="display:none"> The 16 character limit is implemented on the client side. Just issue your own request: <PRE> https://google-gruyere.appspot.com/382665580745386307547168512335551204731/saveprofile?action=new&uid=administrator|admin|author&pw=secret </PRE> <P>Again, this restriction should be implemented on the server side, not just the client side.</P> </DIV> </DIV> </DIV> <BR><BR> <H2><A name="3__cross_site_request_forgery"> </A> Cross-Site Request Forgery (XSRF) </H2> <P> The previous section said "If the user submits a form that says they wish to purchase an item, it's OK to trust that data." That's true as long as it really was the user that submitted the form. If your site is vulnerable to XSS, then the attacker can fake any request as if it came from the user. But even if you've protected against XSS, there's another attack that you need to protect against: cross-site request forgery. </P><P> When a browser makes requests to a site, it always sends along any cookies it has for that site, regardless of where the request comes from. Additionally, web servers generally cannot distinguish between a request initiated by a deliberate user action (e.g., user clicking on "Submit" button) versus a request made by the browser without user action (e.g., request for an embedded image in a page). Therefore, if a site receives a request to perform some action (like deleting a mail, changing contact address), it cannot know whether this action was knowingly initiated by the user — even if the request contains authentication cookies. An attacker can use this fact to fool the server into performing actions the user did not intend to perform. </P> <P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xsrf_desc');"><IMG src="/static/closed.gif"> More details </H4> <DIV id="xsrf_desc" style="display:none"> <P> For example, suppose Blogger is vulnerable to XSRF attacks (it isn't). And let us say Blogger has a Delete Blog button on the dashboard that points to this URL: <PRE> https://www.blogger.com/deleteblog.do?blogId=BLOGID </PRE> Bob, the attacker, embeds the following HTML on his web page on <CODE>https://www.evil.example.com</CODE>: </P> <PRE> <img src="https://www.blogger.com/deleteblog.do?blogId=alice's-blog-id" style="display:none"> </PRE> <P> If the victim, Alice, is logged in to <CODE>www.blogger.com</CODE> when she views the above page, here is what happens: </P> <P></P> <UL> <LI> Her browser loads the page from <CODE>https://www.evil.example.com</CODE>. The browser then tries to load all embedded objects in the page, including the <CODE>img</CODE> shown above. </LI> <LI> The browser makes a request to <CODE>https://www.blogger.com/deleteblog.do?blogId=alice's-blog-id</CODE> to load the image. Since Alice is logged into Blogger — that is, she has a Blogger cookie — the browser also sends that cookie in the request. </LI> <LI> Blogger verifies the cookie is a valid session cookie for Alice. It verifies that the blog referenced by <CODE>alice's-blog-id</CODE> is owned by Alice. It deletes Alice's blog. </LI> <LI> Alice has no idea what hit her. </LI> </UL> <P> In this sample attack, since each user has their own blog id, the attack has to be specifically targeted to a single person. In many cases, though, requests like these don't contain any user-specific data. </P></DIV> </DIV> <P></P> <H3><A name="3__xsrf_challenge"> </A> XSRF Challenge </H3> <P> The goal here is to find a way to perform an account changing action on behalf of a logged in Gruyere user without their knowledge. Assume you can get them to visit a web page under your control. </P> <P> <IMG src="/static/cheese_b.png" style="vertical-align:middle; padding-right: 5px;"> <B> Find a way to get someone to delete one of their Gruyere snippets.</B> </P> <P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xsrf_hint');"><IMG src="/static/closed.gif"> Hint </H4> <DIV id="xsrf_hint" style="display:none"> <P> What is the URL used to delete a snippet? Look at the URL associated with the "X" next to a snippet. </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xsrf_soln');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="xsrf_soln" style="display:none"> <P> <B>To exploit,</B> lure a user to visit a page that makes the following request: </P> <P></P> <PRE> https://google-gruyere.appspot.com/382665580745386307547168512335551204731/deletesnippet?index=0 </PRE> <P> To be especially sneaky, you could set your Gruyere icon to this URL and the victim would be exploited when they visited the main page. </P> <P> <B>To fix,</B> we should first change <CODE>/deletesnippet</CODE> to work via a <CODE>POST</CODE> request since this is a state changing action. In the HTML form, change <CODE>method='get'</CODE> to <CODE>method='post'</CODE>. On the server side, <CODE>GET</CODE> and <CODE>POST</CODE> requests look the same except that they usually call different handlers. For example, Gruyere uses Python's BaseHTTPServer which calls <CODE>do_GET</CODE> for <CODE>GET</CODE> requests and <CODE>do_POST</CODE> for <CODE>POST</CODE> requests. </P><P> <B>However</B>, note that changing to <CODE>POST</CODE> is not enough of a fix in itself! (Gruyere uses <CODE>GET</CODE> requests exclusively because it makes hacking it a bit easier. <CODE>POST</CODE> is not more secure than <CODE>GET</CODE> but it is more correct: browsers may re-issue <CODE>GET</CODE> requests which can result in an action getting executed more than once; browsers won't reissue <CODE>POST</CODE> requests without user consent.) Then we need to pass a unique, unpredictable authorization token to the user and require that it get sent back before performing the action. For this authorization token, <CODE>action_token</CODE>, we can use a hash of the value of the user's cookie appended to a current timestamp and include this token in all state-changing HTTP requests as an additional HTTP parameter. The reason we use <CODE>POST</CODE> over <CODE>GET</CODE> requests is that if we pass <CODE>action_token</CODE> as a URL parameter, it might leak via HTTP Referer headers. The reason we include the timestamp in our hash is so that we can expire old tokens, which mitigates the risk if it leaks. </P> <P> When a request is processed, Gruyere should regenerate the token and compare it with the value supplied with the request. If the values are equal, then it should perform the action. Otherwise, it should reject it. The functions that generate and verify the tokens look like this: </P> <P></P> <PRE> def _GenerateXsrfToken(self, cookie): """Generates a timestamp and XSRF token for all state changing actions.""" timestamp = time.time() return timestamp + "|" + (str(hash(cookie_secret + cookie + timestamp))) def _VerifyXsrfToken(self, cookie, action_token): """Verifies an XSRF token included in a request.""" # First, make sure that the token isn't more than a day old. (action_time, action_hash) = action_token.split("|", 1) now = time.time() if now - 86400 > float(action_time): return False # Second, regenerate it and check that it matches the user supplied value hash_to_verify = str(hash(cookie_secret + cookie + action_time) return action_hash == hash_to_verify </PRE> <P> <B>Oops!</B> There's several things wrong with these functions. </P><H4 style="cursor: pointer" onclick="toggleBlock(this, 'xsrf_whats_wrong');"><IMG src="/static/closed.gif"> What's missing? </H4> <DIV id="xsrf_whats_wrong" style="display:none"> <P> By including the time in the token, we prevent it from being used forever, but if an attacker were to gain access to a copy of the token, they could reuse it as many times as they wanted within that 24 hour period. The expiration time of a token should be set to a small value that represents the reasonable length of time it will take the user to make a request. This token also doesn't protect against an attack where a token for one request is intercepted and then used for a different request. As suggested by the name <CODE>action_token</CODE>, the token should be tied to the specific state changing action being performed, such as the URL of the page. A better signature for <CODE>_GenerateXsrfToken</CODE> would be <CODE>(self, cookie, action)</CODE>. For very long actions, like editing snippets, a script on the page could query the server to update the token when the user hits submit. (But read the next section about XSSI to make sure that an attacker won't be able to read that new token.)</P> <P> XSRF vulnerabilities exist because an attacker can easily script a series of requests to an application and than force a user to execute them by visiting some page. To prevent this type of attack, you need to introduce some value that can't be predicted or scripted by an attacker for <B>every account changing</B> request. Some application frameworks have XSRF protection built in: they automatically include a unique token in every response and verify it on every POST request. Other frameworks provide functions that you can use to do that. If neither of these cases apply, then you'll have to <A href="https://www.google.com/search?q=preventing+(XSRF+OR+CSRF)" target="_top">build your own</A>. Be careful of things that don't work: using <CODE>POST</CODE> instead of <CODE>GET</CODE> is advisable but not sufficient by itself, checking Referer headers is insufficient, and copying cookies into hidden form fields can make your cookies less secure. </P> <!--MARK-7--> </DIV> </DIV> <BR><P></P> </DIV> <H2><A name="3__cross_site_script_inclusion"> </A> Cross Site Script Inclusion (XSSI) </H2> <P> Browsers prevent pages of one domain from reading pages in other domains. But they do not prevent pages of a domain from referencing resources in other domains. In particular, they allow images to be rendered from other domains and scripts to be executed from other domains. An included script doesn't have its own security context. It runs in the security context of the page that included it. For example, if <CODE>www.evil.example.com</CODE> includes a script hosted on <CODE>www.google.com</CODE> then that script runs in the <CODE>evil</CODE> context not in the <CODE>google</CODE> context. So any user data in that script will "leak." </P> <!--MARK-8--> <P></P> <H3><A name="3__xssi_challenge"> </A> XSSI Challenge </H3> <P> <IMG src="/static/cheese_w.png" style="vertical-align:middle; padding-right: 5px;"> <B>Find a way to read someone else's private snippet using XSSI.</B> </P> <P> That is, create a page on another web site and put something in that page that can read your private snippet. (You don't need to post it to a web site: you can just create a <CODE>.html</CODE> in your home directory and double click on it to open in a browser.) </P> <P></P> <DIV style="margin-left:18pt"> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xssi_hint1');"><IMG src="/static/closed.gif"> Hint 1 </H4> <P></P> <DIV id="xssi_hint1" style="display:none"> <P> You can run a script from another domain by adding <PRE> <SCRIPT src="https://google-gruyere.appspot.com/382665580745386307547168512335551204731/..."></SCRIPT> </PRE> to your HTML file. What scripts does Gruyere have? </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xssi_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="xssi_hint2" style="display:none"> <P> <CODE><A href="/code/?resources/feed.gtl">feed.gtl</A></CODE> is a script. Given that, how can you get the private snippet out of the script? </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xssi_sol');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="xssi_sol" style="display:none"> <P> <B>To exploit,</B> put this in an html file: </P> <P></P> <PRE> <script> function _feed(s) { alert("Your private snippet is: " + s['private_snippet']); } </script> <script src="https://google-gruyere.appspot.com/382665580745386307547168512335551204731/feed.gtl"></script> </PRE> <P> When the script in <CODE><A href="/code/?resources/feed.gtl">feed.gtl</A></CODE> is executed, it runs in the context of the attacker's web page and uses the <CODE>_feed</CODE> function which can do whatever it wants with the data, including sending it off to another web site. </P> <P> You might think that you can fix this by eliminating the function call and just having the bare expression. That way, when the script is executed by inclusion, the response will be evaluated and then discarded. That won't work because JavaScript allows you to do things like redefine default constructors. So when the object is evaluated, the hosting page's constructors are invoked, which can do whatever they want with the values. </P> <!--MARK-9--> <P> <B>To fix,</B> there are several changes you can make. Any one of these changes will prevent currently possible attacks, but if you add several layers of protection ("<a href="https://www.google.com/search?q=%22defense+in+depth%22+security">defense in depth</a>") you protect against the possibility that you get one of the protections wrong and also against future browser vulnerabilities. First, use an XSRF token as discussed earlier to make sure that JSON results containing confidential data are only returned to your own pages. Second, your JSON response pages should only support <CODE>POST</CODE> requests, which prevents the script from being loaded via a script tag. Third, you should make sure that the script is not executable. The standard way of doing this is to append some non-executable prefix to it, like <CODE>])}while(1);</x></CODE>. A script running in the same domain can read the contents of the response and strip out the prefix, but scripts running in other domains can't. </P> <P> NOTE: Making the script not executable is more subtle than it seems. It's possible that what makes a script executable may change in the future if new scripting features or languages are introduced. Some people suggest that you can protect the script by making it a comment by surrounding it with <CODE>/*</CODE> and <CODE>*/</CODE>, but that's not as simple as it might seem. (Hint: what if someone included <CODE>*/</CODE> in one of their snippets?) </P> <P> There's <A href="https://www.google.com/search?q=%22cross+site+script+inclusion%22" target="_top">much more to XSSI</A> than this. There's a variation of JSON called JSONP which you should avoid using because it allows script injection <I>by design</I>. And there's <A href="https://www.google.com/search?q=E4X+markup+security" target="_top">E4X</A> (Ecmascript for XML) which can result in your HTML file being parsed as a script. Surprisingly, one way to protect against E4X attacks is to put some invalid XML in your files, like the <CODE></x></CODE> above. </P> <P></P> </DIV> </DIV> <BR><P></P> <FONT SIZE="+2"> <A href="/part4">Continue >></A> </FONT><BR> <BR> <P style="font-size:x-small"> © Google 2017 <A href="https://www.google.com/intl/en/policies/terms/">Terms of Service</A> <BR>The code portions of this codelab are licensed under the Creative Commons Attribution-No Derivative Works 3.0 United States license <<A href="https://creativecommons.org/licenses/by-nd/3.0/us/">https://creativecommons.org/licenses/by-nd/3.0/us</A>>. Brief excerpts of the code may be used for educational or instructional purposes provided this notice is kept intact. Except as otherwise noted the remainder of this codelab is licensed under the Creative Commons Attribution 3.0 United States license <<A href="https://creativecommons.org/licenses/by/3.0/us/">https://creativecommons.org/licenses/by/3.0/us</A>>. </P> </BODY></HTML> |
| URL | http://google-gruyere.appspot.com/part4 |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 363 bytes. |
GET http://google-gruyere.appspot.com/part4 HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/ Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 244 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: c1a0138a874d01326b102c3fecac557e Date: Tue, 06 Feb 2024 17:42:21 GMT Server: Google Frontend Content-Length: 24149 |
| Response Body - size: 24,149 bytes. |
<HTML><HEAD><META http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<TITLE>Web Application Exploits and Defenses</TITLE> <LINK type="text/css" rel="stylesheet" href="../static/codelab.css"> <!--MARK-Z--> <SCRIPT> function toggleBlock(heading, whichID) { var image = heading.childNodes[0]; var block = document.getElementById(whichID); if (block) { if (getDisplay(block) == 'block') { block.style.display = 'none'; image.src = 'static/closed.gif'; } else { // "none" or "" block.style.display = 'block'; image.src = 'static/open.gif'; } } } function getDisplay(block) { var value = block.style.display; if (!value) { if (document.defaultView) { var computedStyle = document.defaultView.getComputedStyle(block, ""); value = computedStyle.getPropertyValue('display'); } else if (block.currentStyle) { value = block.currentStyle.display; } } return value; } </SCRIPT> </HEAD> <BODY bgcolor="#ffffff"> <DIV class="banner"></DIV> <H1><FONT size="+2"><IMG src="/static/gruyere-78.png" style="vertical-align:middle"> Web Application Exploits and Defenses (Part 4)</FONT> </H1> <P> A Codelab by Bruce Leban, Mugdha Bendre, and Parisa Tabriz </P> <DIV class="printable"> <DIV class="column1"> Table of Contents <UL> <LI class="L1" style="padding-top:0pt"> <A href="/#0__hackers">Beat the hackers</A></LI> <LI class="L1" style="padding-top:0pt"> <A href="/#0__gruyere">Gruyere</A></LI> <LI class="L1"> <A href="/part1#1__setup">Set-up</A> <UL> <LI class="L2"> <A href="/part1#1__reset_button">Reset Button</A></LI> <LI class="L2"> <A href="/part1#1__about_the_code">About the Code</A></LI> <LI class="L2"> <A href="/part1#1__features_and_technologies">Features and Technologies</A></LI> </UL></LI> <LI class="L1"> <A href="/part1#1__using_gruyere">Using Gruyere</A></LI> <LI class="L1"> <A href="/part2#2__cross_site_scripting">Cross-Site Scripting (XSS)</A> <UL> <LI class="L2"> <A href="/part2#2__xss_challenge">XSS Challenges</A></LI> <LI class="L2"> <A href="/part2#2__file_upload_xss">File Upload XSS</A></LI> <LI class="L2"> <A href="/part2#2__reflected_xss">Reflected XSS</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss">Stored XSS</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss_via_html_attribute">Stored XSS via HTML Attribute</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss_via_ajax">Stored XSS via AJAX</A></LI> <LI class="L2"> <A href="/part2#2__reflected_xss_via_ajax">Reflected XSS via AJAX</A></LI> <LI class="L2"> <A href="/part2#2__more_about_xss">More about XSS</A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__client_state_manipulation">Client-State Manipulation</A> <UL> <LI class="L2"> <A href="/part3#3__elevation_of_privilege"> Elevation of Privilege </A></LI> <LI class="L2"> <A href="/part3#3__cookie_manipulation"> Cookie Manipulation </A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__cross_site_request_forgery">Cross-Site Request Forgery (XSRF)</A> <UL> <LI class="L2"> <A href="/part3#3__xsrf_challenge">XSRF Challenge</A></LI> <LI class="L2"> <A href="/part3#3__more_about_preventing_xsrf">More about preventing XSRF</A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__cross_site_script_inclusion">Cross Site Script Inclusion (XSSI)</A> <UL> <LI class="L2"> <A href="/part3#3__xssi_challenge">XSSI Challenge</A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__path_traversal">Path Traversal</A> <UL> <LI class="L2"> <A href="/part4#4__information_disclosure_path_traversal">Information disclosure via path traversal </A></LI> <LI class="L2"> <A href="/part4#4__data_tampering_path_traversal">Data tampering via path traversal </A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__denial_of_service">Denial of Service</A> <UL> <LI class="L2"> <A href="/part4#4__dos_quit_server">DoS - Quit the Server</A></LI> <LI class="L2"> <A href="/part4#4__dos_overload_server">DoS - Overloading the Server</A></LI> <LI class="L2"> <A href="/part4#4__more_dos">More on Denial of Service</A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__code_execution">Code Execution</A> <UL> <LI class="L2"> <A href="/part4#4__code_execution_challenge">Code Execution Challenge</A></LI> <LI class="L2"> <A href="/part4#4__more_code_execution">More on Remote Code Execution</A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__configuration_vulnerabilities">Configuration Vulnerabilities</A> <UL> <LI class="L2"> <A href="/part5#5__information_disclosure_config_1">Information disclosure #1</A></LI> <LI class="L2"> <A href="/part5#5__information_disclosure_config_2">Information disclosure #2</A></LI> <LI class="L2"> <A href="/part5#5__information_disclosure_bug_3">Information disclosure #3 </A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__ajax_vulnerabilities">AJAX vulnerabilities</A> <UL> <LI class="L2"> <A href="/part5#5__dos_via_ajax">DoS via AJAX</A></LI> <LI class="L2"> <A href="/part5#5__phishing_via_ajax">Phishing via AJAX</A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__other_vulnerabilities"> Other Vulnerabilities </A> </H2> <UL> <LI class="L2"> <A href="/part5#5__buffer_and_integer_overflow">Buffer Overflow and Integer Overflow</A></LI> <LI class="L2"> <A href="/part5#5__sql_injection"> SQL Injection </A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__after_the_codelab">After the Codelab</A></LI> </UL> </DIV> <DIV class="column2"> <P></P> <P></P> <P> <NOAUTOLINK> </NOAUTOLINK></P> <H2><A name="4__path_traversal"> </A> Path Traversal </H2> <P> Most web applications serve static resources like images and CSS files. Frequently, applications simply serve all the files in a folder. If the application isn't careful, the user can use a path traversal attack to read files from other folders that they shouldn't have access to. For example, in both Windows and Linux, <CODE>..</CODE> represents the parent directory, so if you can inject <CODE>../</CODE> in a path you can "escape" to the parent directory. </P> <P> If an attacker knows the structure of your file system, then they can craft a URL that will traverse out of the installation directory to <CODE>/etc</CODE>. For example, if Picasa was vulnerable to path traversal (it isn't) and the Picasa servers use a Unix-like system, then the following would retrieve the password file: </P> <P></P> <PRE> https://www.picasa.com/../../../../../../../etc/passwd </PRE> <P></P> <P></P> <H3><A name="4__information_disclosure_path_traversal"> </A> Information disclosure via path traversal </H3> <P> <IMG src="/static/cheese_bw.png" style="vertical-align:middle; padding-right: 5px;"> <B>Find a way to read <CODE>secret.txt</CODE> from a running Gruyere server.</B> </P> <P> Amazingly, this attack is not even necessary in many cases: people often install applications and never change the defaults. So the first thing an attacker would try is the default value. </P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'read_secret_txt_hint');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="read_secret_txt_hint" style="display:none"> <P> This isn't a black box attack because you need to know that the <CODE>secret.txt</CODE> file exists, where it's stored, and where Gruyere stores its resource files. You don't need to look at any source code. </P> <P></P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'read_secret_txt_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="read_secret_txt_hint2" style="display:none"> <P> How does the server know which URLs represent resource files? You can use curl or a web proxy to craft request URLs that some browsers may not allow. </P> <P></P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'read_secret_txt_sol');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="read_secret_txt_sol" style="display:none"> <P> <B>To exploit,</B> you can steal <CODE>secret.txt</CODE> via this URL: </P> <P></P> <PRE> https://google-gruyere.appspot.com/382665580745386307547168512335551204731/../secret.txt </PRE> <P> Some browsers, like Firefox and Chrome, optimize out <CODE>../</CODE> in URLs. This doesn't provide any security protection because an attacker will use <CODE>%2f</CODE> to represent <CODE>/</CODE> in the URL; or a tool like curl, a web proxy or a browser that doesn't do that optimization. But if you test your application with one of these browsers to see if you're vulnerable, you might think you were protected when you're not. </P> <P> <B>To fix,</B> we need to prevent access to files outside the resources directory. Validating file paths is a bit tricky as there are various ways to hide path elements like "../" or "~" that allow escaping out of the resources folder. The best protection is to only serve specific resource files. You can either hardcode a list or when your application starts, you can crawl the resource directory and build a list of files. Then only accept requests for those files. You can even do some optimization here like caching small files in memory which will make your application faster. If you are going to try to file path validation, you need to do it on the final path, not on the URL, as there are numerous ways to represent the same characters in URLs. <I>Note: Changing file permissions will NOT work. Gruyere has to be able to read this file.</I> </P> <P></P> </DIV> </DIV> <P></P> <H3><A name="4__data_tampering_path_traversal"> </A> Data tampering via path traversal </H3> <P> <IMG src="/static/cheese_bw.png" style="vertical-align:middle; padding-right: 5px;"> <B>Find a way to replace <CODE>secret.txt</CODE> on a running Gruyere server.</B> </P> <P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'write_secret_txt_hint');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="write_secret_txt_hint" style="display:none"> <P> Again, this isn't a black box attack because you need to know about the directory structure that Gruyere uses, specifically where uploaded files are stored. </P> <P></P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'write_secret_txt_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="write_secret_txt_hint2" style="display:none"> <P> If I log in as user <CODE>brie</CODE> and upload a file, where does the server store it? Can you trick the server into uploading a file to <CODE>../../secret.txt</CODE>? </P> <P></P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'write_secret_txt_sol');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="write_secret_txt_sol" style="display:none"> <P> <B>To exploit,</B> create a new user named <CODE>..</CODE> and upload your new <CODE>secret.txt</CODE>. You could also create a user named <CODE>brie/../..</CODE>. </P> <P> <B>To fix,</B> you should escape dangerous characters in the username (replacing them with safe characters) before using it. It was earlier suggested that we should restrict the characters allowed in a username, but it probably didn't occur to you that <CODE>"."</CODE> was a dangerous character. It's worth noting that there's a vulnerability unique to Windows servers with this implementation. On Windows, filenames are not case sensitive but Gruyere usernames are. So one user can attack another user's files by creating a similar username that differs only in case, e.g., <CODE>BRIE</CODE> instead of <CODE>brie</CODE>. So we need to not just escape unsafe characters but convert the username to a canonical form that is different for different usernames. Or we could avoid all these issues by assigning each user a unique identifier instead. </P> <P> <B>Oops!</B> This doesn't completely solve the problem. Even with the above fix in place, there is another way to perform this attack. Can you find it? <H4 style="cursor: pointer" onclick="toggleBlock(this, 'another_write_secret_hint');"><IMG src="/static/closed.gif"> Hint </H4> <DIV id="another_write_secret_hint" style="display:none"> <P> Are there any limits on the filename when you do an upload? You may need to use a special tool like <CODE>curl</CODE> or a web proxy to perform this attack. </P> <P></P> </DIV> </P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'write_secret_txt_sol2');"><IMG src="/static/closed.gif"> Another Exploit and Fix </H4> <DIV id="write_secret_txt_sol2" style="display:none"> <P> Surprisingly, you can upload a file named <CODE>../secret.txt</CODE>. Gruyere provides no protection against this attack. Most browsers won't let you upload that file but, again, you can do it with curl or other tools. You need the same kind of protection when writing files as you do on read. </P> <P> As a general rule, you should never store user data in the same place as your application files but that alone won't protect against these attacks since if the user can inject <CODE>../</CODE> into the file path, they can traverse all the way to the root of the file system and then back down to the normal install location of your application (or even the Python interpreter itself). </P></DIV></DIV> </DIV> <BR><P></P> <H2><A name="4__denial_of_service"> </A> Denial of Service </H2> <P> A denial of service (DoS) attack is an attempt to make a server unable to service ordinary requests. A common form of DoS attack is sending more requests to a server than it can handle. The server spends all its time servicing the attacker's requests that it has very little time to service legitimate requests. Protecting an application against these kinds of DoS attacks is outside the scope of this codelab. And attacking Gruyere in this way would be interpreted as an attack on App Engine. </P><P> Hackers can also prevent a server from servicing requests by taking advantage of server bugs, such as sending requests that crash a server, make it run out of memory, or otherwise cause it fail serving legitimate requests in some way. In the next few challenges, you'll take advantage of bugs in Gruyere to perform DoS attacks. </P> <H3><A name="4__dos_quit_server"> </A> DoS - Quit the Server </H3> The simplest form of denial of service is shutting down a service. <P> <IMG src="/static/cheese_bw.png" style="vertical-align:middle; padding-right: 5px;"> <B>Find a way to make the server quit.</B> </P> <P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'dos_quit_hint');"><IMG src="/static/closed.gif"> Hint </H4> <DIV id="dos_quit_hint" style="display:none"> <P> How does an administrator make the server quit? The server management page is <CODE><A href="/code/?resources/manage.gtl">manage.gtl</A></CODE>. </P> <P></P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'dos_quit_soln');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="dos_quit_soln" style="display:none"> <P> <B>To exploit,</B> make a request to <CODE>https://google-gruyere.appspot.com/382665580745386307547168512335551204731/quitserver</CODE>. You should need to be logged in as an administrator to do this, but you don't. </P><P> This is another example of a common bug. The server protects against non-administrators accessing certain URLs but the list includes <CODE>/quit</CODE> instead of the actual URL <CODE>/quitserver</CODE>. </P> <P> <B>To fix,</B> add <CODE>/quitserver</CODE> to the URLS only accessible to administrators: </P><PRE> _PROTECTED_URLS = [ "/quitserver", "/reset" ] </PRE> <P> <!--MARK-A--> <B>Oops!</B> This doesn't completely solve the problem. The <CODE>reset</CODE> URL is in the protected list. Can you figure out how to access it? </P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'dos_bonus_hint');"><IMG src="/static/closed.gif"> Hint </H4> <DIV id="dos_bonus_hint" style="display:none"> <P> Look carefully at the code that handles URLs and checks for protected ones. </P></DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'dos_bonus_soln');"><IMG src="/static/closed.gif"> Another Exploit and Fix </H4> <DIV id="dos_bonus_soln" style="display:none"> <P> <B>To exploit,</B> use <CODE>https://google-gruyere.appspot.com/382665580745386307547168512335551204731/RESET</CODE>. The check for protected urls is case sensitive. After doing that check, it capitalizes the string to look up the implementation. This is a classic check/use bug where the condition being checked does not match the actual use. This vulnerability is worse than the previous one because it exposes all the protected urls. </P><P> <B>To fix,</B> put the security check inside the dangerous functions rather than outside them. That ensures that no matter how we get there, the security check can't be skipped. </P></DIV> </DIV> </DIV> <P> <H3><A name="4__dos_overload_server"></A> DoS - Overloading the Server </H3> <P> <IMG src="/static/cheese_b.png" style="vertical-align:middle; padding-right: 5px;"> <B>Find a way to overload the server when it processes a request.</B> </P> <P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'dos_overload_hint1');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="dos_overload_hint1" style="display:none"> <P> You can upload a template that does this. </P></DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'dos_overload_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="dos_overload_hint2" style="display:none"> <P> Every page includes the <CODE><A href="/code/?resources/menubar.gtl">menubar.gtl</A></CODE> template. Can you figure out how to make that template overload the server? </P></DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'dos_overload_soln');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="dos_overload_soln" style="display:none"> <P> <B>To exploit,</B> create a file named <CODE>menubar.gtl</CODE> containing: <PRE> [[include:menubar.gtl]]DoS[[/include:menubar.gtl]] </PRE> and upload it to the <CODE>resources</CODE> directory using a path traversal attack, e.g., creating a user named <CODE>../resources</CODE>. </P> <P> <B>To fix,</B> implement the protections against path traversal and uploading templates discussed earlier. </P> </DIV> <B>NOTE:</B> After performing the previous exploit, you'll need to push the <A href="/part1#1__reset_button">reset button</A>. </P> </DIV> <H3><A name="4__more_dos"> </A> More on Denial of Service </H3> <P> Unlike a well defined vulnerability like XSS or XSRF, denial of service describes a wide class of attacks. This might mean bringing your service down or flooding your inbox so you can't receive legitimate mail. Some things to consider: </P> <P></P> <UL> <LI><P> If you were evil and greedy, how quickly could you take down your application or starve all of its resources? For example, is it possible for a user to upload their hard drive to your application? Entering the attacker's mindset can help identify DoS points in your application. Additionally, think about where the computationally and memory intensive tasks are in your application and put safeguards in place. Do sanity checks on input values. </P> <LI><P>Put monitoring in place so you can detect when you are under attack and enforce per user quotas and rate limiting to ensure that a small subset of users cannot starve the rest. Abusive patterns could include increased memory usage, higher latency, or more requests or connections than usual. </P> </UL> <!--MARK-B--> <BR><P></P> <H2><A name="4__code_execution"> </A> Code Execution </H2> <P> If an attacker can execute arbitrary code remotely on your server, it's usually game over. They may be able to take control over the running program or potentially break out the process to open a new shell on the computer. From here, it's usually not hard to compromise the entire machine the server is running on. </P> <P> Similar to information disclosure and denial of service, there is no recipe or specific defense to prevent remote code execution. The program must perform validation of all user input before handling it and where possible, implement functions with least privilege rights. This topic can't be done justice in just a short paragraph, but know that this is likely the scariest results a security bug can have and trumps any of the above attacks. </P> <P></P> <H3><A name="4__code_execution_challenge"> </A> Code Execution Challenge </H3> <P> <IMG src="/static/cheese_w.png" style="vertical-align:middle; padding-right: 5px;"> <B>Find a code execution exploit.</B> </P> <P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'code_upload_hint');"><IMG src="/static/closed.gif"> Hint </H4> <DIV id="code_upload_hint" style="display:none"> <P> You need to use two previous exploits. </P></DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'code_upload_soln');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="code_upload_soln" style="display:none"> <P> <B>To exploit,</B> make a copy of <CODE><A href="/code/?gtl.py">gtl.py</A></CODE> (or <CODE><A href="/code/?sanitize.py">sanitize.py</A></CODE>) and add some exploit code. Now you can either upload a file named <CODE>../gtl.py</CODE> or create a user named <CODE>..</CODE> and upload <CODE>gtl.py</CODE>. Then, make the server quit by browsing to <CODE>https://google-gruyere.appspot.com/382665580745386307547168512335551204731/quitserver</CODE>. When the server restarts, your code will run. </P> <P> This attack was possible because Gruyere has permission to both read and write files in the Gruyere directory. Applications should run with the minimal privileges possible. </P> <P> Why would you attack <CODE>gtl.py</CODE> or <CODE>sanitize.py</CODE> rather than <CODE>gruyere.py</CODE>? When an attacker has a choice, they would usually choose to attack the infrastructure rather than the application itself. The infrastructure is less likely to be updated and less likely to be noticed. When was the last time you checked that no one had replaced <CODE>python.exe</CODE> with a trojan? </P> <P> <B>To fix,</B> fix the two previous exploits. </P> <P></P> </DIV> </DIV> <P></P> <H3><A name="4__more_code_execution"> </A> More on Remote Code Execution </H3> <P> Even though there is no single or simple defense to remote code execution, here is a short list of some preventative measures: </P> <UL> <LI><P><B>Least Privilege:</B> Always run your application with the <A href="https://www.google.com/search?q=least+privileges" target="_top">least privileges</A> it needs. </P> <LI><P><B>Application Level Checks:</B> Avoid passing user input directly into commands that evaluate arbitrary code, like <CODE>eval()</CODE> or <CODE>system()</CODE>. Instead, use the user input as a switch to choose from a set of developer controlled commands. </P> <LI><P><B>Bounds Checks:</B> Implement proper bounds checks for non-safe languages like C++. Avoid <A href="https://www.google.com/search?q=unsafe+string+functions" target="_top">unsafe string functions</A>. Keep in mind that even safe languages like Python and Java use native libraries. </P> </UL> <!--MARK-C--> <BR><P></P> <FONT SIZE="+2"> <A href="/part5">Continue >></A> </FONT><BR> <BR> <P style="font-size:x-small"> © Google 2017 <A href="https://www.google.com/intl/en/policies/terms/">Terms of Service</A> <BR>The code portions of this codelab are licensed under the Creative Commons Attribution-No Derivative Works 3.0 United States license <<A href="https://creativecommons.org/licenses/by-nd/3.0/us/">https://creativecommons.org/licenses/by-nd/3.0/us</A>>. Brief excerpts of the code may be used for educational or instructional purposes provided this notice is kept intact. Except as otherwise noted the remainder of this codelab is licensed under the Creative Commons Attribution 3.0 United States license <<A href="https://creativecommons.org/licenses/by/3.0/us/">https://creativecommons.org/licenses/by/3.0/us</A>>. </P> </BODY></HTML> |
| URL | http://google-gruyere.appspot.com/part5 |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 363 bytes. |
GET http://google-gruyere.appspot.com/part5 HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/ Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 244 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: 0734f8d6045b2c21582855d246b3c640 Date: Tue, 06 Feb 2024 17:42:21 GMT Server: Google Frontend Content-Length: 25303 |
| Response Body - size: 25,303 bytes. |
<HTML><HEAD><META http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<TITLE>Web Application Exploits and Defenses</TITLE> <LINK type="text/css" rel="stylesheet" href="../static/codelab.css"> <!--MARK-Z--> <SCRIPT> function toggleBlock(heading, whichID) { var image = heading.childNodes[0]; var block = document.getElementById(whichID); if (block) { if (getDisplay(block) == 'block') { block.style.display = 'none'; image.src = 'static/closed.gif'; } else { // "none" or "" block.style.display = 'block'; image.src = 'static/open.gif'; } } } function getDisplay(block) { var value = block.style.display; if (!value) { if (document.defaultView) { var computedStyle = document.defaultView.getComputedStyle(block, ""); value = computedStyle.getPropertyValue('display'); } else if (block.currentStyle) { value = block.currentStyle.display; } } return value; } </SCRIPT> </HEAD> <BODY bgcolor="#ffffff"> <DIV class="banner"></DIV> <H1><FONT size="+2"><IMG src="/static/gruyere-78.png" style="vertical-align:middle"> Web Application Exploits and Defenses (Part 5)</FONT> </H1> <P> A Codelab by Bruce Leban, Mugdha Bendre, and Parisa Tabriz </P> <DIV class="printable"> <DIV class="column1"> Table of Contents <UL> <LI class="L1" style="padding-top:0pt"> <A href="/#0__hackers">Beat the hackers</A></LI> <LI class="L1" style="padding-top:0pt"> <A href="/#0__gruyere">Gruyere</A></LI> <LI class="L1"> <A href="/part1#1__setup">Set-up</A> <UL> <LI class="L2"> <A href="/part1#1__reset_button">Reset Button</A></LI> <LI class="L2"> <A href="/part1#1__about_the_code">About the Code</A></LI> <LI class="L2"> <A href="/part1#1__features_and_technologies">Features and Technologies</A></LI> </UL></LI> <LI class="L1"> <A href="/part1#1__using_gruyere">Using Gruyere</A></LI> <LI class="L1"> <A href="/part2#2__cross_site_scripting">Cross-Site Scripting (XSS)</A> <UL> <LI class="L2"> <A href="/part2#2__xss_challenge">XSS Challenges</A></LI> <LI class="L2"> <A href="/part2#2__file_upload_xss">File Upload XSS</A></LI> <LI class="L2"> <A href="/part2#2__reflected_xss">Reflected XSS</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss">Stored XSS</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss_via_html_attribute">Stored XSS via HTML Attribute</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss_via_ajax">Stored XSS via AJAX</A></LI> <LI class="L2"> <A href="/part2#2__reflected_xss_via_ajax">Reflected XSS via AJAX</A></LI> <LI class="L2"> <A href="/part2#2__more_about_xss">More about XSS</A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__client_state_manipulation">Client-State Manipulation</A> <UL> <LI class="L2"> <A href="/part3#3__elevation_of_privilege"> Elevation of Privilege </A></LI> <LI class="L2"> <A href="/part3#3__cookie_manipulation"> Cookie Manipulation </A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__cross_site_request_forgery">Cross-Site Request Forgery (XSRF)</A> <UL> <LI class="L2"> <A href="/part3#3__xsrf_challenge">XSRF Challenge</A></LI> <LI class="L2"> <A href="/part3#3__more_about_preventing_xsrf">More about preventing XSRF</A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__cross_site_script_inclusion">Cross Site Script Inclusion (XSSI)</A> <UL> <LI class="L2"> <A href="/part3#3__xssi_challenge">XSSI Challenge</A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__path_traversal">Path Traversal</A> <UL> <LI class="L2"> <A href="/part4#4__information_disclosure_path_traversal">Information disclosure via path traversal </A></LI> <LI class="L2"> <A href="/part4#4__data_tampering_path_traversal">Data tampering via path traversal </A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__denial_of_service">Denial of Service</A> <UL> <LI class="L2"> <A href="/part4#4__dos_quit_server">DoS - Quit the Server</A></LI> <LI class="L2"> <A href="/part4#4__dos_overload_server">DoS - Overloading the Server</A></LI> <LI class="L2"> <A href="/part4#4__more_dos">More on Denial of Service</A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__code_execution">Code Execution</A> <UL> <LI class="L2"> <A href="/part4#4__code_execution_challenge">Code Execution Challenge</A></LI> <LI class="L2"> <A href="/part4#4__more_code_execution">More on Remote Code Execution</A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__configuration_vulnerabilities">Configuration Vulnerabilities</A> <UL> <LI class="L2"> <A href="/part5#5__information_disclosure_config_1">Information disclosure #1</A></LI> <LI class="L2"> <A href="/part5#5__information_disclosure_config_2">Information disclosure #2</A></LI> <LI class="L2"> <A href="/part5#5__information_disclosure_bug_3">Information disclosure #3 </A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__ajax_vulnerabilities">AJAX vulnerabilities</A> <UL> <LI class="L2"> <A href="/part5#5__dos_via_ajax">DoS via AJAX</A></LI> <LI class="L2"> <A href="/part5#5__phishing_via_ajax">Phishing via AJAX</A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__other_vulnerabilities"> Other Vulnerabilities </A> </H2> <UL> <LI class="L2"> <A href="/part5#5__buffer_and_integer_overflow">Buffer Overflow and Integer Overflow</A></LI> <LI class="L2"> <A href="/part5#5__sql_injection"> SQL Injection </A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__after_the_codelab">After the Codelab</A></LI> </UL> </DIV> <DIV class="column2"> <P></P> <P></P> <P> <NOAUTOLINK> </NOAUTOLINK></P> <H2><A name="5__configuration_vulnerabilities"> </A> Configuration Vulnerabilities </H2> <P> Applications are often installed with default settings that attackers can use to attack them. This is particularly an issue with third party software where an attacker has easy access to a copy of the same application or framework you are running. Hackers know the default account names and passwords. For example, looking at the contents of <CODE><A href="/code/?data.py">data.py</A></CODE> you know that there's a default administrator account named 'admin' with the password 'secret'. </P> <P> Configuration vulnerabilities also include features that increase attack surface. A common example is a feature that is on by default but you are not using, so you didn't configure it and the default configuration is vulnerable. It also includes debug features like status pages or dumping stack traces on failures. </P> <P></P> <H3><A name="5__information_disclosure_config_1"> </A> Information disclosure #1 </H3> <P> <IMG src="/static/cheese_w.png" style="vertical-align:middle; padding-right: 5px;"> <B>Read the contents of the database off of a running server by exploiting a configuration vulnerability.</B> </P> <P>You should look through the Gruyere code looking for default configurations or debug features that don't belong there.</P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'disclosure_1_hint');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="disclosure_1_hint" style="display:none"> <P> Look at all the files installed with Gruyere. Are there any files that shouldn't be there? </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'disclosure_1_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="disclosure_1_hint2" style="display:none"> <P> Look for a <CODE>.gtl</CODE> file that isn't referenced anywhere. </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'disclosure_1_sol');"><IMG src="/static/closed.gif"> Exploit and Fixes </H4> <DIV id="disclosure_1_sol" style="display:none"> <P> <B>To exploit,</B> you can use the debug dump page <CODE><A href="/code/?resoources/dump.gtl">dump.gtl</A></CODE> to display the contents of the database via the following URL: </P><PRE> https://google-gruyere.appspot.com/382665580745386307547168512335551204731/dump.gtl </PRE> <P> <B>To fix,</B> always make sure debug features are not installed. In this case, delete <CODE>dump.gtl</CODE>. This is an example of the kind of debug feature that might be left in an application by mistake. If a debug feature like this is necessary, then it needs to be carefully locked down: only admin users should have access and only requests from debug IP addresses should be accepted. </P><P> This exploit exposes the users' passwords. Passwords should never be stored in cleartext. Instead, you should use <A href="https://www.google.com/search?q=password+hashing">password hashing</A>. The idea is that to authenticate a user, you don't need to know their password, only be convinced that the user knows it. When the user sets their password, you store only a cryptographic hash of the password and a salt value. When the user re-enters their password later, you recompute the hash and if it matches you conclude the password is correct. If an attacker obtains the hash value, it's very difficult for them to reverse that to find the original password. (Which is a good thing, since despite <A href="https://www.google.com/search?q=choosing+a+good+password">lots of advice</A> to the contrary, users frequently use the same weak passwords for multiple sites.) </P></DIV> </DIV> <P></P> <H3><A name="5__information_disclosure_config_2"> </A> Information disclosure #2 </H3> <P> <IMG src="/static/cheese_w.png" style="vertical-align:middle; padding-right: 5px;"> <B>Even after implementing the fix described above, an attacker can undo it and execute the attack! How can that be?</B> </P> <P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'disclosure_2_hint');"><IMG src="/static/closed.gif"> Hint </H4> <DIV id="disclosure_2_hint" style="display:none"> <P> You can upload a file of any type. </P> <P></P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'disclosure_2_sol');"><IMG src="/static/closed.gif"> Exploit and Fixes </H4> <DIV id="disclosure_2_sol" style="display:none"> <P> <B>To exploit,</B> Gruyere allows the user to upload files of any type, including <CODE>.gtl</CODE> files. So the attacker can simply upload their own copy of <CODE><A href="/code/?resources/dump.gtl">dump.gtl</A></CODE> or a similar file and than access it. In fact, as noted earlier, hosting arbitrary content on the server is a major security risk whether it's HTML, JavaScript, Flash or something else. Allowing a file with an unknown file type may lead to a security hole in the future. </P> <P> <B>To fix,</B> we should do several things: </P><OL> <LI> Only files that are part of Gruyere should be treated as templates. </LI> <LI> Don't store user uploaded files in the same place as application files. </LI> <LI> Consider limiting the types of files that can be uploaded (via a whitelist). </LI> </OL> </DIV> </DIV> <H3><A name="5__information_disclosure_bug_3"> </A> Information disclosure #3 </H3> <P> <IMG src="/static/cheese_w.png" style="vertical-align:middle; padding-right: 5px;"> <B>Even after implementing the fixes described above, a similar attack is still possible through a different attack vector. Can you find it?</B> </P> <P> This attack isn't a a configuration vulnerability, just bad code. </P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'disclosure_3_hint1');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="disclosure_3_hint1" style="display:none"> <P>You can insert something in your private snippet which will display the contents of the database.</P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'disclosure_3_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="disclosure_3_hint2" style="display:none"> <P>This attack is closely related to the previous ones. There is a bug in the code that expands templates that you can exploit.</P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'disclosure_3_sol');"><IMG src="/static/closed.gif"> Exploit and Fixes </H4> <DIV id="disclosure_3_sol" style="display:none"> <P> There is a defect in Gruyere's template expansion code that reparses expanded variables. Specifically, when expanding a block it expands variables in the block. Then it parses the block as a template and expands variables again, </P> <B>To exploit,</B> add this to your private snippet: <PRE> {{_db:pprint}} </PRE> </P> <P> <B>To fix,</B> modify the template code so it never reparses inserted variable values. The defect in the code is due to the fact that <CODE>ExpandTemplate</CODE> calls <CODE>_ExpandBlocks</CODE> followed by <CODE>_ExpandVariables</CODE>, but <CODE>_ExpandBlocks</CODE> calls <CODE>ExpandTemplate</CODE> on nested blocks. So if a variable is expanded inside a nested block and contains something that looks like a variable template, it will get expanded a second time. That sounds complicated because it is complicated. Parsing blocks and variables separately is a fundamental flaw in the design of the expander, so the fix is non-trivial. </P> <P> This exploit is possible because the template language allows arbitrary database access. It would be safer if the templates were only allowed to access data specifically provided to them. For example, a template could have an associated database query and only the data matched by that query would be passed to the template. This would limit the scope of a bug like this to data that the user was already allowed to access. </P> </DIV> </DIV> <BR><BR> </P><H2><A name="5__ajax_vulnerabilities"> </A> AJAX vulnerabilities </H2> Bad AJAX code allows attackers to modify parts of your application in ways that you might not expect. In traditional client development, there is a clear separation between the application and the data it displays. That's not true in web applications as the next two attacks will make clear. <P></P> <H3><A name="5__dos_via_ajax"> </A> DoS via AJAX </H3> <P> <IMG src="/static/cheese_b.png" style="vertical-align:middle; padding-right: 5px;"> <B>Find an attack that prevents users from seeing their private snippets on the home page.</B> (The attack should be triggered after clicking the refresh link and without using XSS.) </P> <P></P> <DIV style="margin-left:18pt"> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'dom_hint');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="dom_hint" style="display:none"> <P> Can you figure out how to change the value of the private snippet in the AJAX response? </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'dom_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="dom_hint2" style="display:none"> <P> What happens if a JSON object has a duplicate key value? </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'dom_sol');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="dom_sol" style="display:none"> <P> <B>To exploit,</B> create a user named <CODE>private_snippet</CODE> and create at least one snippet. The JSON response will then be <CODE>{'private_snippet' : <user's private snippet>, ..., 'private_snippet' : <attacker's snippet>}</CODE> and the attacker's snippet replaces the user's. </P> <P> <B>To fix,</B> the AJAX code needs to make sure that the data only goes where it's supposed to go. The flaw here is that the JSON structure is not robust. A better structure would be <CODE>[<private_snippet>, {<user> : <snippet>,...}]</CODE>. </P> <P></P> </DIV> </DIV> <P></P> <H3><A name="5__phishing_via_ajax"> </A> Phishing via AJAX </H3> <P> While the previous attack may seem like a minor inconvenience, careless DOM manipulation can lead to much more serious problems. </P> <P> <IMG src="/static/cheese_b.png" style="vertical-align:middle; padding-right: 5px;"> <B>Find a way to change the sign in link in the upper right corner to point to <CODE>https://evil.example.com</CODE>.</B> </P> <P> (The attack should be triggered after clicking the refresh link and without using XSS or a script.) </P> <P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'phishing_hint');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="phishing_hint" style="display:none"> <P> Look at what the script does to replace the snippets on the page. Can you get it to replace the sign in link? </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'phishing_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="phishing_hint2" style="display:none"> <P> Look at the AJAX code to see how it replaces each snippet, and then look at the structure of the home page and see if you can see what else you might be able to replace. (You can't just replace the sign in link. You'll have to replace a bit more.) </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'phishing_sol');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="phishing_sol" style="display:none"> <P> <B>To exploit,</B> create a user named <CODE>menu-right</CODE> and publish a snippet that looks exactly like the right side of the menu bar. </P> <P></P> <PRE> <a href='https://evil.example.com/login'>Sign in</a> | <a href='https://evil.example.com/newaccount.gtl'>Sign up</a> </PRE> <P> If the user is already logged in, the menu bar will look wrong. But that's ok, since there's a good chance the user will just think they somehow accidentally got logged out of the web site and log in again. </P> <P> <B>To fix,</B> the process of modifying the DOM needs to be made more robust. When user values are used in as DOM element identifiers, you should ensure that there can't be a conflict as there is here, for example, applying a prefix to user values like <CODE>id="user_<USER>"</USER></CODE>. Even better, use your own identifiers rather than user values. </P> <P> This spoofing attack is easily detected when the user clicks Sign in and ends up at <CODE>evil.example.com</CODE>. A clever attacker could do something harder to detect, like replacing the Sign in link with a script that renders the sign in form on the current page with the form submission going to their server. </P> <P></P> </DIV> </DIV> <BR><P></P> <P></P> <H2><A name="5__other_vulnerabilities"> </A> Other Vulnerabilities </H2> <H3><A name="5__buffer_and_integer_overflow"> </A> Buffer Overflow and Integer Overflow </H3> <DIV><P>A <A href="https://www.google.com/search?q=buffer+overflow" target="_top">buffer overflow</A> vulnerability exists when an application does not properly guard its buffers and allow user data to write past the end of a buffer. This excess data can modify other variables, including pointers and function return addresses, leading to arbitrary code execution. Historically, buffer overflow vulnerabilities have been responsible for some of the most widespread internet attacks including <a href="https://www.google.com/search?q=sql+slammer">SQL Slammer<a>, <a href="https://www.google.com/search?q=blaster+worm">Blaster</a> and <a href="https://www.google.com/search?q=code+red+worm">Code Red</a> computer worms. The PS2, Xbox and Wii have all been hacked using buffer overflow exploits. </P> <P>While not as well known, <A href="https://www.google.com/search?q=integer+overflow+vulnerability" target="_top">integer overflow</A> vulnerabilities can be just as dangerous. Any time an integer computation silently returns an incorrect result, the application will operate incorrectly. In the best case, the application fails. In the worst case, there is a security bug. For example, if an application checks that <CODE>length + 1 < limit</CODE> then this will succeed if <CODE>length</CODE> is the largest positive integer value, which can then expose a buffer overflow vulnerability. </P> <P>This codelab doesn't cover overflow vulnerabilities because Gruyere is written in Python, and therefore not vulnerable to typical buffer and integer overflow problems. Python won't allow you to read or write outside the bounds of an array and integers can't overflow. While C and C++ programs are most commonly known to expose these vulnerabilities, other languages are not immune. For example, while Java was designed to prevent buffer overflows, it silently ignores integer overflow. </P> <P> Like all applications, Gruyere is vulnerable to platform vulnerabilities. That is, if there are security bugs in the platforms that Gruyere is built on top of, then those bugs would also apply to Gruyere. Gruyere's platform includes: the Python runtime system and libraries, AppEngine, the operating system that Gruyere runs on and the client side software (including the web browser) that users use to run Gruyere. While platform vulnerabilities are important, they are outside the scope of this codelab as you generally can't fix platform vulnerabilities by making changes to your application. Fixing platform vulnerabilities yourself is also not practical for many people, but you can mitigate your risks by making sure that you are diligent in applying security updates as they are released by platform vendors. </P> </DIV> <!--MARK-D--> <H3><A name="5__sql_injection"> </A> SQL Injection </H3> <DIV><P>Just as XSS vulnerabilities allow attackers to inject script into web pages, <a href="https://www.google.com/search?q=sql+injection">SQL injection</a> vulnerabilities allow attackers to inject arbitrary scripts into SQL queries. When a SQL query is executed it can either read or write data, so an attacker can use SQL injection to read your entire database as well as overwrite it, as described in the classic <a href="https://xkcd.com/327/">Bobby Tables</a> XKCD comic. If you use SQL, the most important advice is to avoid building queries by string concatenation: use API calls instead. This codelab doesn't cover SQL injection because Gruyere doesn't use SQL. </P> </DIV> <BR><BR> <H2><A name="5__after_the_codelab"> </A> After the Codelab </H2> <P> We hope that you found this codelab instructive. If you want more practice, there are many more security bugs in Gruyere than the ones described above. You should attack your own application using what you've learned and write unit tests to verify that these bugs are not present and won't get introduced in the future. You should also consider using <A href="https://www.google.com/search?q=fuzz+testing" target="_top">fuzz testing</A> tools. For more information about security at Google, please visit our <A href="https://security.googleblog.com/" target="_top">blog</A> or our <A href="https://www.google.com/about/appsecurity/" target="_top">corporate security page</A>. </P> <P> If you'd like to share this codelab with others, please consider tweeting or buzzing about it or posting one of these badges on your blog or personal page: <!--MARK-E--> </P> <div> <table style="border-collapse:collapse;border:solid 1 black" cellpadding="5"> <tr><td style="border:solid 1 black;text-align:center;vertical-align:middle"> <a href="https://google-gruyere.appspot.com/"> <img src="../static/gruyere-badge.png" style="padding:4pt" border="0" alt="Learn how to make web apps more secure. Do the Gruyere codelab."></a> </td><td style="border:solid 1 black;vertical-align:middle"> <pre style="margin:0"> <a href="https://google-gruyere.appspot.com/"> <img src="//google-gruyere.appspot.com/static/gruyere-badge.png" style="padding:4pt" border="0" alt="Learn how to make web apps more secure. Do the Gruyere codelab."></a> </pre> <div align="center" style="margin:0">(Line breaks above should be copied verbatim.)</div> </td></tr><tr><td style="border:solid 1 black;text-align:center;vertical-align:middle"> <a href="https://google-gruyere.appspot.com/"> <img src="../static/gruyere-40.png" style="padding:4pt" border="0" title="Learn how to make web apps more secure. Do the Gruyere codelab." alt="Learn how to make web apps more secure. Do the Gruyere codelab."></a> </td><td style="border:solid 1 black;vertical-align:middle"> <pre style="margin:0"> <a href="https://google-gruyere.appspot.com/"> <img src="//google-gruyere.appspot.com/static/gruyere-40.png" style="padding:4pt" border="0" title="Learn how to make web apps more secure. Do the Gruyere codelab." alt="Learn how to make web apps more secure. Do the Gruyere codelab."></a> </pre> <div align="center" style="margin:0">(Line breaks above should be copied verbatim.)</div> </td></tr></table></div> <!--MARK-F--> <!--NEXTLINK--> <BR> <P style="font-size:x-small"> © Google 2017 <A href="https://www.google.com/intl/en/policies/terms/">Terms of Service</A> <BR>The code portions of this codelab are licensed under the Creative Commons Attribution-No Derivative Works 3.0 United States license <<A href="https://creativecommons.org/licenses/by-nd/3.0/us/">https://creativecommons.org/licenses/by-nd/3.0/us</A>>. Brief excerpts of the code may be used for educational or instructional purposes provided this notice is kept intact. Except as otherwise noted the remainder of this codelab is licensed under the Creative Commons Attribution 3.0 United States license <<A href="https://creativecommons.org/licenses/by/3.0/us/">https://creativecommons.org/licenses/by/3.0/us</A>>. </P> </BODY></HTML> |
| URL | http://google-gruyere.appspot.com/start |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 313 bytes. |
GET http://google-gruyere.appspot.com/start HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/robots.txt |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 354 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-Type: text/html; charset=utf-8 Pragma: no-cache Set-Cookie: GRUYERE_ID=382665580745386307547168512335551204731; Path=/ X-Cloud-Trace-Context: cbdf509c9c2a3e252715ab2ea38a1140 Date: Tue, 06 Feb 2024 17:42:17 GMT Server: Google Frontend Content-Length: 681 Expires: Tue, 06 Feb 2024 17:42:17 GMT |
| Response Body - size: 681 bytes. |
<HTML>
<STYLE> body, th, td, form { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; } h1 { color: #dd0000; } </STYLE> <TITLE>Start Gruyere</TITLE> <BODY> <H1>Start Gruyere</H1> Your Gruyere instance id is 382665580745386307547168512335551204731. <br><br><span style="color:red"><b>WARNING: Gruyere is not secure.<br> Do not upload any personal or private data.</b></span> <br><br>By using Gruyere you agree to the <A href="https://www.google.com/intl/en/policies/terms/">terms of service</A>. <H2><A HREF="/382665580745386307547168512335551204731">Agree & Start</A></H2> </BODY></HTML> |
| URL | http://google-gruyere.appspot.com/static/codeindex.html |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 384 bytes. |
GET http://google-gruyere.appspot.com/static/codeindex.html HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/code/ Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 277 bytes. |
HTTP/1.1 200 OK
Date: Tue, 06 Feb 2024 17:42:23 GMT Expires: Tue, 06 Feb 2024 17:52:23 GMT Cache-Control: public, max-age=600 ETag: "3m8CBg" X-Cloud-Trace-Context: fb63ced220be6e1e9458c0baf24f1622 Content-Type: text/html Server: Google Frontend content-length: 2188 |
| Response Body - size: 2,188 bytes. |
<HTML>
<HEAD> <TITLE>Gruyere Code</TITLE> <STYLE> a {color:black; text-decoration:none} a:visited{color:#6600cc;} a:focus, a:hover {color:#003399; text-decoration: underline;} a:active{color:#cc33cc;} big {color: #660066; } </STYLE> </HEAD> <BODY> <TT> <B><BIG><BIG>Gruyere<BR>Code</BIG></BIG></B><BR><BR> <A href="/code/gruyere.py" target="codepage">gruyere.py</A><BR> <A href="/code/data.py" target="codepage">data.py</A><BR> <A href="/code/sanitize.py" target="codepage">sanitize.py</A><BR> <A href="/code/gtl.py" target="codepage">gtl.py</A><BR> <A href="/code/secret.txt" target="codepage">secret.txt</A><BR> <BR> resources/<BR> <A href="/code/resources/base.css" target="codepage">base.css</A><BR> <A href="/code/resources/dump.gtl" target="codepage">dump.gtl</A><BR> <A href="/code/resources/editprofile.gtl" target="codepage">editprofile.gtl</A><BR> <A href="/code/resources/error.gtl" target="codepage">error.gtl</A><BR> <A href="/code/resources/feed.gtl" target="codepage">feed.gtl</A><BR> <A href="/code/resources/home.gtl" target="codepage">home.gtl</A><BR> <A href="/code/resources/lib.js" target="codepage">lib.js</A><BR> <A href="/code/resources/login.gtl" target="codepage">login.gtl</A><BR> <A href="/code/resources/manage.gtl" target="codepage">manage.gtl</A><BR> <A href="/code/resources/menubar.gtl" target="codepage">menubar.gtl</A><BR> <A href="/code/resources/newaccount.gtl" target="codepage">newaccount.gtl</A><BR> <A href="/code/resources/newsnippet.gtl" target="codepage">newsnippet.gtl</A><BR> <A href="/code/resources/showprofile.gtl" target="codepage">showprofile.gtl</A><BR> <A href="/code/resources/snippets.gtl" target="codepage">snippets.gtl</A><BR> <A href="/code/resources/upload.gtl" target="codepage">upload.gtl</A><BR> <A href="/code/resources/upload2.gtl" target="codepage">upload2.gtl</A><BR> <BR> <I><A href="/gruyere-code.zip" target="_top">Download zip file</A></I><BR> <BR> <A href="/" target="_top"><< back to codelab</A> </TT> </BODY> </HTML> |
| URL | https://google-gruyere.appspot.com/ |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 364 bytes. |
GET https://google-gruyere.appspot.com/ HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/part5 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 301 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: 8fa64cb955098684db5c4eca2daf22f7 Date: Tue, 06 Feb 2024 17:42:23 GMT Server: Google Frontend Content-Length: 11506 Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 |
| Response Body - size: 11,506 bytes. |
<HTML><HEAD><META http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<TITLE>Web Application Exploits and Defenses</TITLE> <LINK type="text/css" rel="stylesheet" href="../static/codelab.css"> <!--MARK-Z--> <SCRIPT> function toggleBlock(heading, whichID) { var image = heading.childNodes[0]; var block = document.getElementById(whichID); if (block) { if (getDisplay(block) == 'block') { block.style.display = 'none'; image.src = 'static/closed.gif'; } else { // "none" or "" block.style.display = 'block'; image.src = 'static/open.gif'; } } } function getDisplay(block) { var value = block.style.display; if (!value) { if (document.defaultView) { var computedStyle = document.defaultView.getComputedStyle(block, ""); value = computedStyle.getPropertyValue('display'); } else if (block.currentStyle) { value = block.currentStyle.display; } } return value; } </SCRIPT> </HEAD> <BODY bgcolor="#ffffff"> <DIV class="banner"></DIV> <H1><FONT size="+2"><IMG src="/static/gruyere-78.png" style="vertical-align:middle"> Web Application Exploits and Defenses <!--PART#--></FONT> </H1> <P> A Codelab by Bruce Leban, Mugdha Bendre, and Parisa Tabriz </P> <DIV class="printable"> <DIV class="column1"> Table of Contents <UL> <LI class="L1" style="padding-top:0pt"> <A href="/#0__hackers">Beat the hackers</A></LI> <LI class="L1" style="padding-top:0pt"> <A href="/#0__gruyere">Gruyere</A></LI> <LI class="L1"> <A href="/part1#1__setup">Set-up</A> <UL> <LI class="L2"> <A href="/part1#1__reset_button">Reset Button</A></LI> <LI class="L2"> <A href="/part1#1__about_the_code">About the Code</A></LI> <LI class="L2"> <A href="/part1#1__features_and_technologies">Features and Technologies</A></LI> </UL></LI> <LI class="L1"> <A href="/part1#1__using_gruyere">Using Gruyere</A></LI> <LI class="L1"> <A href="/part2#2__cross_site_scripting">Cross-Site Scripting (XSS)</A> <UL> <LI class="L2"> <A href="/part2#2__xss_challenge">XSS Challenges</A></LI> <LI class="L2"> <A href="/part2#2__file_upload_xss">File Upload XSS</A></LI> <LI class="L2"> <A href="/part2#2__reflected_xss">Reflected XSS</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss">Stored XSS</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss_via_html_attribute">Stored XSS via HTML Attribute</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss_via_ajax">Stored XSS via AJAX</A></LI> <LI class="L2"> <A href="/part2#2__reflected_xss_via_ajax">Reflected XSS via AJAX</A></LI> <LI class="L2"> <A href="/part2#2__more_about_xss">More about XSS</A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__client_state_manipulation">Client-State Manipulation</A> <UL> <LI class="L2"> <A href="/part3#3__elevation_of_privilege"> Elevation of Privilege </A></LI> <LI class="L2"> <A href="/part3#3__cookie_manipulation"> Cookie Manipulation </A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__cross_site_request_forgery">Cross-Site Request Forgery (XSRF)</A> <UL> <LI class="L2"> <A href="/part3#3__xsrf_challenge">XSRF Challenge</A></LI> <LI class="L2"> <A href="/part3#3__more_about_preventing_xsrf">More about preventing XSRF</A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__cross_site_script_inclusion">Cross Site Script Inclusion (XSSI)</A> <UL> <LI class="L2"> <A href="/part3#3__xssi_challenge">XSSI Challenge</A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__path_traversal">Path Traversal</A> <UL> <LI class="L2"> <A href="/part4#4__information_disclosure_path_traversal">Information disclosure via path traversal </A></LI> <LI class="L2"> <A href="/part4#4__data_tampering_path_traversal">Data tampering via path traversal </A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__denial_of_service">Denial of Service</A> <UL> <LI class="L2"> <A href="/part4#4__dos_quit_server">DoS - Quit the Server</A></LI> <LI class="L2"> <A href="/part4#4__dos_overload_server">DoS - Overloading the Server</A></LI> <LI class="L2"> <A href="/part4#4__more_dos">More on Denial of Service</A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__code_execution">Code Execution</A> <UL> <LI class="L2"> <A href="/part4#4__code_execution_challenge">Code Execution Challenge</A></LI> <LI class="L2"> <A href="/part4#4__more_code_execution">More on Remote Code Execution</A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__configuration_vulnerabilities">Configuration Vulnerabilities</A> <UL> <LI class="L2"> <A href="/part5#5__information_disclosure_config_1">Information disclosure #1</A></LI> <LI class="L2"> <A href="/part5#5__information_disclosure_config_2">Information disclosure #2</A></LI> <LI class="L2"> <A href="/part5#5__information_disclosure_bug_3">Information disclosure #3 </A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__ajax_vulnerabilities">AJAX vulnerabilities</A> <UL> <LI class="L2"> <A href="/part5#5__dos_via_ajax">DoS via AJAX</A></LI> <LI class="L2"> <A href="/part5#5__phishing_via_ajax">Phishing via AJAX</A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__other_vulnerabilities"> Other Vulnerabilities </A> </H2> <UL> <LI class="L2"> <A href="/part5#5__buffer_and_integer_overflow">Buffer Overflow and Integer Overflow</A></LI> <LI class="L2"> <A href="/part5#5__sql_injection"> SQL Injection </A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__after_the_codelab">After the Codelab</A></LI> </UL> </DIV> <DIV class="column2"> <P></P> <P></P> <P> <NOAUTOLINK> </NOAUTOLINK></P> <STYLE>.column1 {display:none}</STYLE> <BR><P> <H2><A name="0__hackers"></A>Want to beat the hackers at their own game?</H2> <UL> <LI>Learn how hackers find security vulnerabilities! <LI>Learn how hackers exploit web applications! <LI>Learn how to stop them! </UL> </P> <!--MARK-0--> <P> This codelab shows how web application vulnerabilities can be exploited and how to defend against these attacks. The best way to learn things is by doing, so you'll get a chance to do some real penetration testing, actually exploiting a real application. Specifically, you'll learn the following: </P> <P></P> <UL> <LI> How an application can be attacked using common web security vulnerabilities, like cross-site scripting vulnerabilities (XSS) and cross-site request forgery (XSRF). </LI> <LI> How to find, fix, and avoid these common vulnerabilities and other bugs that have a security impact, such as denial-of-service, information disclosure, or remote code execution. </LI> </UL> <P> To get the most out of this lab, you should have some familiarity with how a web application works (e.g., general knowledge of HTML, templates, cookies, AJAX, etc.). </P> <!--MARK-1--> <BR> <BR> <H2><A name="1__gruyere"> </A> Gruyere </H2> <P> <A href="/static/gruyere.png"> <IMG src="/static/gruyere.png" height="285" border="0" style="float:left; vertical-align:middle; margin-right: 10; margin-bottom: 10"> </A> This codelab is built around <B>Gruyere</B> /ɡruːˈjɛər/ <!--groo-yair--> - a small, cheesy web application that allows its users to publish snippets of text and store assorted files. "Unfortunately," Gruyere has multiple security bugs ranging from cross-site scripting and cross-site request forgery, to information disclosure, denial of service, and remote code execution. The goal of this codelab is to guide you through discovering some of these bugs and learning ways to fix them both in Gruyere and in general. </P> <P> The codelab is organized by types of vulnerabilities. In each section, you'll find a brief description of a vulnerability and a task to find an instance of that vulnerability in Gruyere. Your job is to play the role of a malicious hacker and find and exploit the security bugs. In this codelab, you'll use both black-box hacking and white-box hacking. In <B>black box hacking,</B> you try to find security bugs by experimenting with the application and manipulating input fields and URL parameters, trying to cause application errors, and looking at the HTTP requests and responses to guess server behavior. You do not have access to the source code, although understanding how to view source and being able to view http headers (as you can in Chrome or LiveHTTPHeaders for Firefox) is valuable. Using a web proxy like <A href="https://portswigger.net/burp/" target="_top">Burp</A> or <A href="https://www.owasp.org/index.php/OWASP_Zed_Attack_Proxy_Project" target="_top">ZAP</A> may be helpful in creating or modifying requests. In <B>white-box hacking,</B> you have access to the source code and can use automated or manual analysis to identify bugs. You can treat Gruyere as if it's open source: you can read through the source code to try to find bugs. Gruyere is written in Python, so some familiarity with Python can be helpful. However, the security vulnerabilities covered are not Python-specific and you can do most of the lab without even looking at the code. You can run a local instance of Gruyere to assist in your hacking: for example, you can create an administrator account on your local instance to learn how administrative features work and then apply that knowledge to the instance you want to hack. Security researchers use both hacking techniques, often in combination, in real life. </P> <BR clear="left"> We'll tag each challenge to indicate which techniques are required to solve them: <BR><BR> <IMG src="/static/cheese_b.png" style="vertical-align:middle; padding-right: 5px;"> Challenges that can be solved just by using black box techniques.<BR><BR> <IMG src="/static/cheese_w.png" style="vertical-align:middle; padding-right: 5px;"> Challenges that require that you look at the Gruyere source code.<BR><BR> <IMG src="/static/cheese_bw.png" style="vertical-align:middle; padding-right: 5px;"> Challenges that require some specific knowledge of Gruyere that will be given in the first hint. <BR> <P style="color:red"> <B>WARNING:</B> Accessing or attacking a computer system without authorization is illegal in many jurisdictions. While doing this codelab, you are specifically granted authorization to attack the Gruyere application as directed. You may not attack Gruyere in ways other than described in this codelab, nor may you attack App Engine directly or any other Google service. You should use what you learn from the codelab to make your own applications more secure. You should not use it to attack any applications other than your own, and only do that with permission from the appropriate authorities (e.g., your company's security team). </P> <P></P> <FONT SIZE="+2"> <A href="/part1">Continue >></A> </FONT><BR> <BR> <P style="font-size:x-small"> © Google 2017 <A href="https://www.google.com/intl/en/policies/terms/">Terms of Service</A> <BR>The code portions of this codelab are licensed under the Creative Commons Attribution-No Derivative Works 3.0 United States license <<A href="https://creativecommons.org/licenses/by-nd/3.0/us/">https://creativecommons.org/licenses/by-nd/3.0/us</A>>. Brief excerpts of the code may be used for educational or instructional purposes provided this notice is kept intact. Except as otherwise noted the remainder of this codelab is licensed under the Creative Commons Attribution 3.0 United States license <<A href="https://creativecommons.org/licenses/by/3.0/us/">https://creativecommons.org/licenses/by/3.0/us</A>>. </P> </BODY></HTML> |
| URL | https://google-gruyere.appspot.com/382665580745386307547168512335551204731/ |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 451 bytes. |
GET https://google-gruyere.appspot.com/382665580745386307547168512335551204731/ HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: https://google-gruyere.appspot.com/382665580745386307547168512335551204731/quitserver. Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 306 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: 1b18c15e61fbec8b3e4949b4ddc0dc67 Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 3480 Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 |
| Response Body - size: 3,480 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Home</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> <script src="/382665580745386307547168512335551204731/lib.js" text="text/javascript"> </script> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/382665580745386307547168512335551204731/'>Home</a> </span> <span id='menu-right'> <a href='/382665580745386307547168512335551204731/login'>Sign in</a> | <a href='/382665580745386307547168512335551204731/newaccount.gtl'>Sign up</a> </span> </div> <div> <h2 class='has-refresh'>Gruyere: Home</h2> <div class='refresh'><a class='button' onclick='_refreshHome("382665580745386307547168512335551204731")' href='#'>Refresh</a></div> </div> <div class='content'> <table width='100%'> <tr><td colspan='3'><b>Most recent snippets:</b></td></tr> <tr> <td> </td> <td> <b><span style='color:blue'>Cheddar Mac</span></b> </td> <td width='100%'><span id='cheddar'>Gruyere is the cheesiest application on the web.</span> <br> <a href='/382665580745386307547168512335551204731/snippets.gtl?uid=cheddar'>All snippets</a> <a href='https://images.google.com/?q=cheddar+cheese'>Homepage</a> <br> <br> </td> </tr> <tr> <td> </td> <td> <b><span style='color:red; text-decoration:underline'>Brie</span></b> </td> <td width='100%'><span id='brie'>Brie is the queen of the cheeses<span style=color:red>!!!</span></span> <br> <a href='/382665580745386307547168512335551204731/snippets.gtl?uid=brie'>All snippets</a> <a href='https://news.google.com/news/search?q=brie'>Homepage</a> <br> <br> </td> </tr> </table> </div> </body> </html> |
| URL | https://google-gruyere.appspot.com/382665580745386307547168512335551204731/feed.gtl |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 412 bytes. |
GET https://google-gruyere.appspot.com/382665580745386307547168512335551204731/feed.gtl HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/part2 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 305 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: 0d6b4d9f18f7cecd56fd804c68263bbf Date: Tue, 06 Feb 2024 17:42:23 GMT Server: Google Frontend Content-Length: 191 Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 |
| Response Body - size: 191 bytes. |
_feed((
{ "private_snippet": "" ,"cheddar": "Gruyere is the cheesiest application on the web." ,"brie": "Brie is the queen of the cheeses<span style=color:red>!!!</span>" } )) |
| URL | https://google-gruyere.appspot.com/382665580745386307547168512335551204731/login |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 456 bytes. |
GET https://google-gruyere.appspot.com/382665580745386307547168512335551204731/login HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: https://google-gruyere.appspot.com/382665580745386307547168512335551204731/quitserver. Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 306 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: 8b42cbce881c35ee8d87883b85cd8f7f Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 2591 Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 |
| Response Body - size: 2,591 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Login</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/382665580745386307547168512335551204731/'>Home</a> </span> <span id='menu-right'> <a href='/382665580745386307547168512335551204731/login'>Sign in</a> | <a href='/382665580745386307547168512335551204731/newaccount.gtl'>Sign up</a> </span> </div> <div> <h2>Gruyere: Login</h2> </div> <div class='content'> <form method='get' action='/382665580745386307547168512335551204731/login'> <table><tr><td> User name: </td><td> <input type='text' name='uid'> </td></tr><tr><td> Password: </td><td> <input type='password' name='pw'> </td></tr><tr><td></td><td align='right'> <input type='submit' value='Login'> </td></tr></table> </form> </div> </body> </html> |
| URL | https://google-gruyere.appspot.com/382665580745386307547168512335551204731/newaccount.gtl |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 465 bytes. |
GET https://google-gruyere.appspot.com/382665580745386307547168512335551204731/newaccount.gtl HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: https://google-gruyere.appspot.com/382665580745386307547168512335551204731/quitserver. Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 306 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: e1f0d836f241aeea4e12446db611b471 Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 2961 Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 |
| Response Body - size: 2,961 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Profile</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/382665580745386307547168512335551204731/'>Home</a> </span> <span id='menu-right'> <a href='/382665580745386307547168512335551204731/login'>Sign in</a> | <a href='/382665580745386307547168512335551204731/newaccount.gtl'>Sign up</a> </span> </div> <h2>Gruyere: Sign up</h2> <div class='content'> <h3>Sign up for a new account.</h3> <form method='get' action='/382665580745386307547168512335551204731/saveprofile'> <input type='hidden' name='action' value='new'> <table><tr><td> User name: </td><td> <input type='text' name='uid' value='' maxlength='16'> </td></tr> <tr><td> Password: </td><td> <input type='password' name='pw'> <br><span style="color:red"><b>WARNING: Gruyere is not secure.<br> Do not use a password that you use for any real service.<br> Do not upload any personal or private data.</b></span> </td></tr> <input type='hidden' name='is_author' value='True'> <tr><td></td><td align='right'> <input type='submit' value='Create account'> </td></tr> </table> </form> </div> </body> </html> |
| URL | https://google-gruyere.appspot.com/382665580745386307547168512335551204731/quitserver. |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 415 bytes. |
GET https://google-gruyere.appspot.com/382665580745386307547168512335551204731/quitserver. HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/part4 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 306 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: ba514b4b962961f93b1c4fbc21fab959 Date: Tue, 06 Feb 2024 17:42:23 GMT Server: Google Frontend Content-Length: 2246 Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 |
| Response Body - size: 2,246 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Error</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/382665580745386307547168512335551204731/'>Home</a> </span> <span id='menu-right'> <a href='/382665580745386307547168512335551204731/login'>Sign in</a> | <a href='/382665580745386307547168512335551204731/newaccount.gtl'>Sign up</a> </span> </div> <div class='message'>Unrecognized file type (/quitserver.).</div> </body> </html> |
| URL | https://google-gruyere.appspot.com/382665580745386307547168512335551204731/RESET. |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 410 bytes. |
GET https://google-gruyere.appspot.com/382665580745386307547168512335551204731/RESET. HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/part4 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 306 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: 07f510b6cdd84a858cf4d3c73829ba7a Date: Tue, 06 Feb 2024 17:42:23 GMT Server: Google Frontend Content-Length: 2241 Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 |
| Response Body - size: 2,241 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Error</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/382665580745386307547168512335551204731/'>Home</a> </span> <span id='menu-right'> <a href='/382665580745386307547168512335551204731/login'>Sign in</a> | <a href='/382665580745386307547168512335551204731/newaccount.gtl'>Sign up</a> </span> </div> <div class='message'>Unrecognized file type (/RESET.).</div> </body> </html> |
| URL | https://google-gruyere.appspot.com/382665580745386307547168512335551204731/saveprofile?action=update&is_admin=True |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 443 bytes. |
GET https://google-gruyere.appspot.com/382665580745386307547168512335551204731/saveprofile?action=update&is_admin=True HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/part3 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 306 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: 49fab23ad5f50929949b343099495117 Date: Tue, 06 Feb 2024 17:42:23 GMT Server: Google Frontend Content-Length: 2228 Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 |
| Response Body - size: 2,228 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Error</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/382665580745386307547168512335551204731/'>Home</a> </span> <span id='menu-right'> <a href='/382665580745386307547168512335551204731/login'>Sign in</a> | <a href='/382665580745386307547168512335551204731/newaccount.gtl'>Sign up</a> </span> </div> <div class='message'>User does not exist.</div> </body> </html> |
| URL | https://google-gruyere.appspot.com/382665580745386307547168512335551204731/saveprofile?action=update&is_admin=True&uid=username |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 456 bytes. |
GET https://google-gruyere.appspot.com/382665580745386307547168512335551204731/saveprofile?action=update&is_admin=True&uid=username HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/part3 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 306 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: 1c2eebd839ba8c29a02e769d0f003a2e Date: Tue, 06 Feb 2024 17:42:23 GMT Server: Google Frontend Content-Length: 2228 Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 |
| Response Body - size: 2,228 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Error</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/382665580745386307547168512335551204731/'>Home</a> </span> <span id='menu-right'> <a href='/382665580745386307547168512335551204731/login'>Sign in</a> | <a href='/382665580745386307547168512335551204731/newaccount.gtl'>Sign up</a> </span> </div> <div class='message'>User does not exist.</div> </body> </html> |
| URL | https://google-gruyere.appspot.com/code/ |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 369 bytes. |
GET https://google-gruyere.appspot.com/code/ HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/part1 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 303 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: e9bcd98bd046d94c037b912b5eabbbf3;o=1 Date: Tue, 06 Feb 2024 17:42:22 GMT Server: Google Frontend Content-Length: 354 Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 |
| Response Body - size: 354 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN"
"https://www.w3.org/TR/html4/frameset.dtd"> <HTML> <HEAD> <TITLE>Gruyere Code</TITLE> </HEAD> <FRAMESET cols="165,100%"> <FRAME src="/static/codeindex.html"> <FRAME name="codepage" src=""> <NOFRAMES> <A href="/static/codeindex/html">Code Index</A> </NOFRAMES> </FRAMESET> </HTML> |
| URL | https://google-gruyere.appspot.com/part1 |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 365 bytes. |
GET https://google-gruyere.appspot.com/part1 HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: https://google-gruyere.appspot.com/ Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 301 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: ec0d0e195a8e5f5598cf104482954f77 Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 13686 Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 |
| Response Body - size: 13,686 bytes. |
<HTML><HEAD><META http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<TITLE>Web Application Exploits and Defenses</TITLE> <LINK type="text/css" rel="stylesheet" href="../static/codelab.css"> <!--MARK-Z--> <SCRIPT> function toggleBlock(heading, whichID) { var image = heading.childNodes[0]; var block = document.getElementById(whichID); if (block) { if (getDisplay(block) == 'block') { block.style.display = 'none'; image.src = 'static/closed.gif'; } else { // "none" or "" block.style.display = 'block'; image.src = 'static/open.gif'; } } } function getDisplay(block) { var value = block.style.display; if (!value) { if (document.defaultView) { var computedStyle = document.defaultView.getComputedStyle(block, ""); value = computedStyle.getPropertyValue('display'); } else if (block.currentStyle) { value = block.currentStyle.display; } } return value; } </SCRIPT> </HEAD> <BODY bgcolor="#ffffff"> <DIV class="banner"></DIV> <H1><FONT size="+2"><IMG src="/static/gruyere-78.png" style="vertical-align:middle"> Web Application Exploits and Defenses (Part 1)</FONT> </H1> <P> A Codelab by Bruce Leban, Mugdha Bendre, and Parisa Tabriz </P> <DIV class="printable"> <DIV class="column1"> Table of Contents <UL> <LI class="L1" style="padding-top:0pt"> <A href="/#0__hackers">Beat the hackers</A></LI> <LI class="L1" style="padding-top:0pt"> <A href="/#0__gruyere">Gruyere</A></LI> <LI class="L1"> <A href="/part1#1__setup">Set-up</A> <UL> <LI class="L2"> <A href="/part1#1__reset_button">Reset Button</A></LI> <LI class="L2"> <A href="/part1#1__about_the_code">About the Code</A></LI> <LI class="L2"> <A href="/part1#1__features_and_technologies">Features and Technologies</A></LI> </UL></LI> <LI class="L1"> <A href="/part1#1__using_gruyere">Using Gruyere</A></LI> <LI class="L1"> <A href="/part2#2__cross_site_scripting">Cross-Site Scripting (XSS)</A> <UL> <LI class="L2"> <A href="/part2#2__xss_challenge">XSS Challenges</A></LI> <LI class="L2"> <A href="/part2#2__file_upload_xss">File Upload XSS</A></LI> <LI class="L2"> <A href="/part2#2__reflected_xss">Reflected XSS</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss">Stored XSS</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss_via_html_attribute">Stored XSS via HTML Attribute</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss_via_ajax">Stored XSS via AJAX</A></LI> <LI class="L2"> <A href="/part2#2__reflected_xss_via_ajax">Reflected XSS via AJAX</A></LI> <LI class="L2"> <A href="/part2#2__more_about_xss">More about XSS</A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__client_state_manipulation">Client-State Manipulation</A> <UL> <LI class="L2"> <A href="/part3#3__elevation_of_privilege"> Elevation of Privilege </A></LI> <LI class="L2"> <A href="/part3#3__cookie_manipulation"> Cookie Manipulation </A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__cross_site_request_forgery">Cross-Site Request Forgery (XSRF)</A> <UL> <LI class="L2"> <A href="/part3#3__xsrf_challenge">XSRF Challenge</A></LI> <LI class="L2"> <A href="/part3#3__more_about_preventing_xsrf">More about preventing XSRF</A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__cross_site_script_inclusion">Cross Site Script Inclusion (XSSI)</A> <UL> <LI class="L2"> <A href="/part3#3__xssi_challenge">XSSI Challenge</A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__path_traversal">Path Traversal</A> <UL> <LI class="L2"> <A href="/part4#4__information_disclosure_path_traversal">Information disclosure via path traversal </A></LI> <LI class="L2"> <A href="/part4#4__data_tampering_path_traversal">Data tampering via path traversal </A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__denial_of_service">Denial of Service</A> <UL> <LI class="L2"> <A href="/part4#4__dos_quit_server">DoS - Quit the Server</A></LI> <LI class="L2"> <A href="/part4#4__dos_overload_server">DoS - Overloading the Server</A></LI> <LI class="L2"> <A href="/part4#4__more_dos">More on Denial of Service</A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__code_execution">Code Execution</A> <UL> <LI class="L2"> <A href="/part4#4__code_execution_challenge">Code Execution Challenge</A></LI> <LI class="L2"> <A href="/part4#4__more_code_execution">More on Remote Code Execution</A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__configuration_vulnerabilities">Configuration Vulnerabilities</A> <UL> <LI class="L2"> <A href="/part5#5__information_disclosure_config_1">Information disclosure #1</A></LI> <LI class="L2"> <A href="/part5#5__information_disclosure_config_2">Information disclosure #2</A></LI> <LI class="L2"> <A href="/part5#5__information_disclosure_bug_3">Information disclosure #3 </A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__ajax_vulnerabilities">AJAX vulnerabilities</A> <UL> <LI class="L2"> <A href="/part5#5__dos_via_ajax">DoS via AJAX</A></LI> <LI class="L2"> <A href="/part5#5__phishing_via_ajax">Phishing via AJAX</A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__other_vulnerabilities"> Other Vulnerabilities </A> </H2> <UL> <LI class="L2"> <A href="/part5#5__buffer_and_integer_overflow">Buffer Overflow and Integer Overflow</A></LI> <LI class="L2"> <A href="/part5#5__sql_injection"> SQL Injection </A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__after_the_codelab">After the Codelab</A></LI> </UL> </DIV> <DIV class="column2"> <P></P> <P></P> <P> <NOAUTOLINK> </NOAUTOLINK></P> <H2><A name="1__setup"> </A> Setup </H2> <P> To access Gruyere, go to <CODE><A href="/start">https://google-gruyere.appspot.com/start</A></CODE>. AppEngine will start a new instance of Gruyere for you, assign it a unique id and redirect you to <CODE>https://google-gruyere.appspot.com/<!--do not replace-->123/</CODE> (where <CODE>123</CODE> is your unique id). Each instance of Gruyere is "sandboxed" from the other instances so your instance won't be affected by anyone else using Gruyere. You'll need to use your unique id instead of <CODE>123</CODE> in all the examples. If you want to share your instance of Gruyere with someone else (e.g., to show them a successful attack), just share the full URL with them including your unique id. </P> <P>The Gruyere source code is available online so that you can use it for white-box hacking. You can browse the source code at <A href="/code/">https://google-gruyere.appspot.com/code/</A> or download all the files from <A href="/gruyere-code.zip">https://google-gruyere.appspot.com/gruyere-code.zip</A>. If want to debug it or actually try fixing the bugs, you can download it and run it locally. You do not need to run Gruyere locally in order to do the lab. </P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'running_locally');"><IMG src="/static/closed.gif"> Running locally </H4> <DIV id="running_locally" style="display:none"> <P> <FONT color="#ff0000"> <B>WARNING:</B> Because Gruyere is very vulnerable, it includes some protection against being exploited by an external attacker when run locally. You'll see these parts of the code marked DO NOT CHANGE. Gruyere only accepts requests from localhost and uses a random unique id in the URL. However, it's difficult to fully protect against an external attack. And if you make changes to Gruyere you could make it more vulnerable to a real attack. Therefore, you should close other web pages while running Gruyere locally and you should make sure that no other user is logged in to the machine you are using. </FONT> </P> <P> To run Gruyere locally, you'll first need to install Python 2.7, if you don't already have it. Gruyere was developed and tested with version 2.7 and may not work with other versions of Python. You can download it from <A href="https://www.python.org/downloads/" target="_top">python.org</A>. Download Gruyere itself from <A href="/gruyere-code.zip">https://google-gruyere.appspot.com/gruyere-code.zip</A> and unpack it to your local disk. Then to run the application, simply type: </P> <P></P> <PRE> $ cd <gruyere-directory> $ ./gruyere.py</PRE> <P> You'll need to replace <CODE>google-gruyere.appspot.com</CODE> in all the examples with <CODE>localhost:8008</CODE> in addition to replacing <CODE>123</CODE> with your unique id. Note that the unique id appears in a different location. There are a few other small differences between running Gruyere locally vs. accessing the instance on App Engine. The most obvious is that the App Engine version runs in a limited sandbox. So if you do something that puts Gruyere into an infinite loop, the monitor will detect it and kill it. That might not happen when you run it locally, depending on what the loop is doing. </P> </DIV> </DIV> <BR> <H3><A name="1__reset_button"> </A> Reset Button </H3> As noted above, each instance is sandboxed so it can't consume infinite resources and it can't interfere with anyone else's instance. Notwithstanding that, it is possible to put your Gruyere instance into a state where it is completely unusable. If that happens, you can push a magic "reset button" to wipe out all the data in your instance and start from scratch. To do this, visit this URL with your instance id: <PRE> https://google-gruyere.appspot.com/resetbutton/382665580745386307547168512335551204731 </PRE> <BR> <H3><A name="1__about_the_code"> </A> About the Code </H3> <P> Gruyere is small and compact. Here is a quick rundown of the application code: </P><UL> <LI> <CODE><A href="/code/?gruyere.py">gruyere.py</A></CODE> is the main Gruyere web server </LI> <LI> <CODE><A href="/code/?data.py">data.py</A></CODE> stores the default data in the database. There is an administrator account and two default users. </LI> <LI> <CODE><A href="/code/?gtl.py">gtl.py</A></CODE> is the Gruyere template language </LI> <LI> <CODE><A href="/code/?sanitize.py">sanitize.py</A></CODE> is the Gruyere module used for sanitizing HTML to protect the application from security holes. </LI> <LI> <CODE><A href="/code/">resources/...</A></CODE> holds all template files, images, CSS, etc. </LI> </UL> <P></P> <BR> <H3><A name="1__features_and_technologies"> </A> Features and Technologies </H3> <P> Gruyere includes a number of special features and technologies which add attack surface. We'll highlight them here so you'll be aware of them as you try to attack it. Each of these introduces new vulnerabilities. </P> <P></P> <UL> <LI> HTML in Snippets: Users can include a limited subset of HTML in their snippets. </LI> <LI> File upload: Users can upload files to the server, e.g., to include pictures in their snippets. </LI> <LI> Web administration: System administrators can manage the system using a web interface. </LI> <LI> New accounts: Users can create their own accounts. </LI> <LI> Template language: Gruyere Template Language(GTL) is a new language that makes writing web pages easy as the templates connect directly to the database. Documentation for GTL can be found in <CODE><A href="/code/?gtl.py">gruyere/gtl.py</A></CODE>. </LI> <LI> AJAX: Gruyere uses AJAX to implement refresh on the home and snippets page. You should ignore the AJAX parts of Gruyere except for the challenges that specifically tell you to focus on AJAX. <UL> <LI> In a real application, refresh would probably happen automatically, but in Gruyere we've made it manual so that you can be in complete control while you are working with it. When you click the refresh link, Gruyere fetches <CODE><A href="/code/?resources/feed.gtl">feed.gtl</A></CODE> which contains refresh data for the current page and then client-side script uses the browser DOM API (Document Object Model) to insert the new snippets into the page. Since AJAX runs code on the client side, this script is visible to attackers who do not have access to your source code. </LI> </UL> </LI> </UL> <P></P> <BR> <H2><A name="1__using_gruyere"> </A> Using Gruyere </H2> <P> To familiarize yourself with the features of Gruyere, complete the following tasks: </P> <P></P> <UL> <LI> View another user's snippets by following the "All snippets" link on the main page. Also check out what they have their Homepage set to. </LI> <LI> Sign up for an account for yourself to use when hacking. <B>Do not use the same password for your Gruyere account as you use for any real service.</B> </LI> <LI> Fill in your account's profile, including a private snippet and an icon that will be displayed by your name. </LI> <LI> Create a snippet (via "New Snippet") containing your favorite joke. </LI> <LI> Upload a file (via "Upload") to your account. </LI> </UL> <P></P> <P> This covers the basic features provided by Gruyere. Now let's break them! </P> <BR><P></P> <FONT SIZE="+2"> <A href="/part2">Continue >></A> </FONT><BR> <BR> <P style="font-size:x-small"> © Google 2017 <A href="https://www.google.com/intl/en/policies/terms/">Terms of Service</A> <BR>The code portions of this codelab are licensed under the Creative Commons Attribution-No Derivative Works 3.0 United States license <<A href="https://creativecommons.org/licenses/by-nd/3.0/us/">https://creativecommons.org/licenses/by-nd/3.0/us</A>>. Brief excerpts of the code may be used for educational or instructional purposes provided this notice is kept intact. Except as otherwise noted the remainder of this codelab is licensed under the Creative Commons Attribution 3.0 United States license <<A href="https://creativecommons.org/licenses/by/3.0/us/">https://creativecommons.org/licenses/by/3.0/us</A>>. </P> </BODY></HTML> |
| URL | https://google-gruyere.appspot.com/part2 |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 365 bytes. |
GET https://google-gruyere.appspot.com/part2 HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: https://google-gruyere.appspot.com/ Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 301 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: db540cb7d57c537e646ca96132050405 Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 37564 Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 |
| Response Body - size: 37,564 bytes. |
<HTML><HEAD><META http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<TITLE>Web Application Exploits and Defenses</TITLE> <LINK type="text/css" rel="stylesheet" href="../static/codelab.css"> <!--MARK-Z--> <SCRIPT> function toggleBlock(heading, whichID) { var image = heading.childNodes[0]; var block = document.getElementById(whichID); if (block) { if (getDisplay(block) == 'block') { block.style.display = 'none'; image.src = 'static/closed.gif'; } else { // "none" or "" block.style.display = 'block'; image.src = 'static/open.gif'; } } } function getDisplay(block) { var value = block.style.display; if (!value) { if (document.defaultView) { var computedStyle = document.defaultView.getComputedStyle(block, ""); value = computedStyle.getPropertyValue('display'); } else if (block.currentStyle) { value = block.currentStyle.display; } } return value; } </SCRIPT> </HEAD> <BODY bgcolor="#ffffff"> <DIV class="banner"></DIV> <H1><FONT size="+2"><IMG src="/static/gruyere-78.png" style="vertical-align:middle"> Web Application Exploits and Defenses (Part 2)</FONT> </H1> <P> A Codelab by Bruce Leban, Mugdha Bendre, and Parisa Tabriz </P> <DIV class="printable"> <DIV class="column1"> Table of Contents <UL> <LI class="L1" style="padding-top:0pt"> <A href="/#0__hackers">Beat the hackers</A></LI> <LI class="L1" style="padding-top:0pt"> <A href="/#0__gruyere">Gruyere</A></LI> <LI class="L1"> <A href="/part1#1__setup">Set-up</A> <UL> <LI class="L2"> <A href="/part1#1__reset_button">Reset Button</A></LI> <LI class="L2"> <A href="/part1#1__about_the_code">About the Code</A></LI> <LI class="L2"> <A href="/part1#1__features_and_technologies">Features and Technologies</A></LI> </UL></LI> <LI class="L1"> <A href="/part1#1__using_gruyere">Using Gruyere</A></LI> <LI class="L1"> <A href="/part2#2__cross_site_scripting">Cross-Site Scripting (XSS)</A> <UL> <LI class="L2"> <A href="/part2#2__xss_challenge">XSS Challenges</A></LI> <LI class="L2"> <A href="/part2#2__file_upload_xss">File Upload XSS</A></LI> <LI class="L2"> <A href="/part2#2__reflected_xss">Reflected XSS</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss">Stored XSS</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss_via_html_attribute">Stored XSS via HTML Attribute</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss_via_ajax">Stored XSS via AJAX</A></LI> <LI class="L2"> <A href="/part2#2__reflected_xss_via_ajax">Reflected XSS via AJAX</A></LI> <LI class="L2"> <A href="/part2#2__more_about_xss">More about XSS</A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__client_state_manipulation">Client-State Manipulation</A> <UL> <LI class="L2"> <A href="/part3#3__elevation_of_privilege"> Elevation of Privilege </A></LI> <LI class="L2"> <A href="/part3#3__cookie_manipulation"> Cookie Manipulation </A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__cross_site_request_forgery">Cross-Site Request Forgery (XSRF)</A> <UL> <LI class="L2"> <A href="/part3#3__xsrf_challenge">XSRF Challenge</A></LI> <LI class="L2"> <A href="/part3#3__more_about_preventing_xsrf">More about preventing XSRF</A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__cross_site_script_inclusion">Cross Site Script Inclusion (XSSI)</A> <UL> <LI class="L2"> <A href="/part3#3__xssi_challenge">XSSI Challenge</A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__path_traversal">Path Traversal</A> <UL> <LI class="L2"> <A href="/part4#4__information_disclosure_path_traversal">Information disclosure via path traversal </A></LI> <LI class="L2"> <A href="/part4#4__data_tampering_path_traversal">Data tampering via path traversal </A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__denial_of_service">Denial of Service</A> <UL> <LI class="L2"> <A href="/part4#4__dos_quit_server">DoS - Quit the Server</A></LI> <LI class="L2"> <A href="/part4#4__dos_overload_server">DoS - Overloading the Server</A></LI> <LI class="L2"> <A href="/part4#4__more_dos">More on Denial of Service</A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__code_execution">Code Execution</A> <UL> <LI class="L2"> <A href="/part4#4__code_execution_challenge">Code Execution Challenge</A></LI> <LI class="L2"> <A href="/part4#4__more_code_execution">More on Remote Code Execution</A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__configuration_vulnerabilities">Configuration Vulnerabilities</A> <UL> <LI class="L2"> <A href="/part5#5__information_disclosure_config_1">Information disclosure #1</A></LI> <LI class="L2"> <A href="/part5#5__information_disclosure_config_2">Information disclosure #2</A></LI> <LI class="L2"> <A href="/part5#5__information_disclosure_bug_3">Information disclosure #3 </A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__ajax_vulnerabilities">AJAX vulnerabilities</A> <UL> <LI class="L2"> <A href="/part5#5__dos_via_ajax">DoS via AJAX</A></LI> <LI class="L2"> <A href="/part5#5__phishing_via_ajax">Phishing via AJAX</A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__other_vulnerabilities"> Other Vulnerabilities </A> </H2> <UL> <LI class="L2"> <A href="/part5#5__buffer_and_integer_overflow">Buffer Overflow and Integer Overflow</A></LI> <LI class="L2"> <A href="/part5#5__sql_injection"> SQL Injection </A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__after_the_codelab">After the Codelab</A></LI> </UL> </DIV> <DIV class="column2"> <P></P> <P></P> <P> <NOAUTOLINK> </NOAUTOLINK></P> <H2><A name="2__cross_site_scripting"> </A> Cross-Site Scripting (XSS) </H2> <P> Cross-site scripting (XSS) is a vulnerability that permits an attacker to inject code (typically HTML or JavaScript) into contents of a website not under the attacker's control. When a victim views such a page, the injected code executes in the victim's browser. Thus, the attacker has bypassed the browser's <A href="https://www.google.com/search?q=same+origin+policy">same origin policy</A> and can steal victim's private information associated with the website in question. </P> <P> In a <b>reflected XSS</b> attack, the attack is in the request itself (frequently the URL) and the vulnerability occurs when the server inserts the attack in the response verbatim or incorrectly escaped or sanitized. The victim triggers the attack by browsing to a malicious URL created by the attacker. In a <b>stored XSS</b> attack, the attacker stores the attack in the application (e.g., in a snippet) and the victim triggers the attack by browsing to a page on the server that renders the attack, by not properly escaping or sanitizing the stored data. </P> <P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_desc');"><IMG src="/static/closed.gif"> More details </H4> <DIV id="xss_desc" style="display:none"> <P></P> <P> To understand how this could happen: suppose the URL <CODE>https://www.google.com/search?q=flowers</CODE> returns a page containing the HTML fragment </P> <P></P> <PRE> <p>Your search for 'flowers' returned the following results:</p> </PRE> <P> that is, the value of the query parameter <CODE>q</CODE> is inserted verbatim into the page returned by Google. If <CODE>www.google.com</CODE> did not do any validation or escaping of <CODE>q</CODE> (it does), an attacker could craft a link that looks like this:<BR> <PRE> https://www.google.com/search?q=flowers+%3Cscript%3Eevil_script()%3C/script%3E </PRE> and trick a victim into clicking on this link. When a victim loads this link, the following page gets rendered in the victim's browser: </P> <P></P> <PRE> <p>Your search for 'flowers<script>evil_script()</script>' returned the following results:</p> </PRE> <P> And the browser executes <CODE>evil_script()</CODE>. And since the page comes from <CODE>www.google.com</CODE>, <CODE>evil_script()</CODE> is executed in the context of <CODE>www.google.com</CODE> and has access to all the victim's browser state and cookies for that domain. </P> <P> Note that the victim does not even need to explicitly click on the malicious link. Suppose the attacker owns <CODE>www.evil.example.com</CODE>, and creates a page with an <CODE><iframe></CODE> pointing to the malicious link; if the victim visits <CODE>www.evil.example.com</CODE>, the attack will silently be activated. </P> </DIV> </DIV> <P></P> <H3><A name="2__xss_challenge"> </A> XSS Challenges </H3> <P> Typically, if you can get JavaScript to execute on a page when it's viewed by another user, you have an XSS vulnerability. A simple JavaScript function to use when hacking is the <CODE>alert()</CODE> function, which creates a pop-up box with whatever string you pass as an argument. </P> <P>You might think that inserting an alert message isn't terribly dangerous, but if you can inject that, you can inject other scripts that are more malicious. It is not necessary to be able to inject any particular special character in order to attack. If you can inject <CODE>alert(1)</CODE> then you can inject arbitrary script using <CODE>eval(String.fromCharCode(...))</CODE>. </P> <P> Your challenge is to find XSS vulnerabilities in Gruyere. You should look for vulnerabilities both in URLs and in stored data. Since XSS vulnerabilities usually involve applications not properly handling untrusted user data, a common method of attack is to enter random text in input fields and look at how it gets rendered in the response page's HTML source. But before we do that, let's try something simpler. </P> <P></P> <H3><A name="2__file_upload_xss"> </A> File Upload XSS </H3> <P> <IMG src="/static/cheese_b.png" style="vertical-align:middle; padding-right: 5px;"> <B>Can you upload a file that allows you to execute arbitrary script on the <CODE>google-gruyere.appspot.com</CODE> domain?</B> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'upload_xss_hint1');"><IMG src="/static/closed.gif"> Hint </H4> <DIV id="upload_xss_hint1" style="display:none"> <P> You can upload HTML files and HTML files can contain script. </P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'upload_xss_sol');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="upload_xss_sol" style="display:none"> <P><B>To exploit,</B> upload a <CODE>.html</CODE> file containing a script like this: <PRE> <script> alert(document.cookie); </script> </PRE> </P><P> <B>To fix,</B> host the content on a separate domain so the script won't have access to any content from your domain. That is, instead of hosting user content on <CODE>example.com/<I>username</I></CODE> we would host it at <CODE><I>username</I>.usercontent.example.com</CODE> or <CODE><I>username</I>.example-usercontent.com</CODE>. (Including something like "<CODE>usercontent</CODE>" in the domain name avoids attackers registering usernames that look innocent like <CODE>wwww</CODE> and using them for phishing attacks.) <P> </DIV> </DIV> <H3><A name="2__reflected_xss"> </A> Reflected XSS </H3> <P> There's an interesting problem here. Some browsers have built-in protection against reflected XSS attacks. There are also browser extensions like NoScript that provide some protection. If you're using one of those browsers or extensions, you may need to use a different browser or temporarily disable the extension to execute these attacks. </P> <P>At the time this codelab was written, the two browsers which had this protection were IE and Chrome. To work around this, Gruyere automatically includes a <TT>X-XSS-Protection: 0</TT> HTTP header in every response which is recognized by IE and will be recognized by future versions of Chrome. (It's available in the developer channel now.) If you're using Chrome, you can try starting it with the <TT>--disable-xss-auditor</TT> flag by entering one of these commands: <UL><LI>Windows: <TT>"C:\Documents and Settings\USERNAME\Local Settings\Application Data\Google\Chrome\Application\chrome.exe" --disable-xss-auditor</TT> <LI>Mac: <TT>/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --disable-xss-auditor</TT> <LI>GNU/Linux: <TT>/opt/google/chrome/google-chrome --disable-xss-auditor</TT> </UL> If you're using Firefox with the NoScript extension, add <TT>google-gruyere.appspot.com</TT> to the allow list. If you still can't get the XSS attacks to work, try a different browser. </P> <P>You may think that you don't need to worry about XSS if the browser protects against it. The truth is that the browser protection can't be perfect because it doesn't really know your application and therefore there may be ways for a clever hacker to circumvent that protection. The real protection is to not have an XSS vulnerability in your application in the first place. </P> <IMG src="/static/cheese_b.png" style="vertical-align:middle; padding-right: 5px;"> <B>Find a reflected XSS attack. What we want is a URL that when clicked on will execute a script.</B> <P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_url_hint1');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="xss_url_hint1" style="display:none"> <P> What does this URL do? </P> <P></P> <PRE> https://google-gruyere.appspot.com/382665580745386307547168512335551204731/invalid </PRE> <P></P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_url_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="xss_url_hint2" style="display:none"> <P> The most dangerous characters in a URL are <CODE><</CODE> and <CODE>></CODE>. If you can get an application to directly insert what you want in a page and can get those characters through, then you can probably get a script through. Try these: </P> <P></P> <PRE> https://google-gruyere.appspot.com/382665580745386307547168512335551204731/%3e%3c https://google-gruyere.appspot.com/382665580745386307547168512335551204731/%253e%253c https://google-gruyere.appspot.com/382665580745386307547168512335551204731/%c0%be%c0%bc https://google-gruyere.appspot.com/382665580745386307547168512335551204731/%26gt;%26lt; https://google-gruyere.appspot.com/382665580745386307547168512335551204731/%26amp;gt;%26amp;lt; https://google-gruyere.appspot.com/382665580745386307547168512335551204731/\074\x3c\u003c\x3C\u003C\X3C\U003C https://google-gruyere.appspot.com/382665580745386307547168512335551204731/+ADw-+AD4- </PRE> <P> This tries <CODE>></CODE> and <CODE><</CODE> in many different ways that might be able to make it through the URL and get rendered incorrectly using: verbatim (URL %-encoding), double %-encoding, bad UTF-8 encoding, HTML &-encoding, double &-encoding, and several different variations on C-style encoding. View the resulting source and see if any of those work. (Note: literally typing <CODE>><</CODE> in the URL is identical to <CODE>%3e%3c</CODE> because the browser automatically %-encodes those character. If you are trying to want a literal <CODE>></CODE> or <CODE><</CODE> then you will need to use a tool like curl to send those characters in URL.) </P> <P></P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_url_sol');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="xss_url_sol" style="display:none"> <P> <B>To exploit,</B> create a URL like the following and get a victim to click on it: </P> <P></P> <PRE> https://google-gruyere.appspot.com/382665580745386307547168512335551204731/<script>alert(1)</script> </PRE> <P> <B>To fix,</B> you need to escape user input that is displayed in error messages. Error messages are displayed using <CODE><A href="/code/?resources/error.gtl">error.gtl</A></CODE>, but are not escaped in the template. The part of the template that renders the message is <CODE>{{message}}</CODE> and it's missing the modifier that tells it to escape user input. Add the <CODE>:text</CODE> modifier to escape the user input: </P><PRE> <div class="message">{{_message:text}}</div> </PRE> <P> This flaw would have been best mitigated by a design that escapes all output by default and only displays raw HTML when explicitly tagged to do so. There are also <A href="https://www.google.com/search?q=XSS+auto+escaping" target="_top">autoescaping</A> features available in many template systems. </P> <!--MARK-2--> <P></P> </DIV> </DIV> <P></P> <H3><A name="2__stored_xss"> </A> Stored XSS </H3> <P> <IMG src="/static/cheese_b.png" style="vertical-align:middle; padding-right: 5px;"> <B>Now find a stored XSS. What we want to do is put a script in a place where Gruyere will serve it back to another user.</B> </P> The most obvious place that Gruyere serves back user-provided data is in a snippet (ignoring uploaded files which we've already discussed.)<P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_stored_hint1');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="xss_stored_hint1" style="display:none"> <P> Put this in a snippet and see what you get: </P> <P></P> <PRE> <script>alert(1)</script> </PRE> <P> There are many different ways that script can be embedded in a document. </P> <P></P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_stored_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="xss_stored_hint2" style="display:none"> <P> Hackers don't limit themselves to valid HTML syntax. Try some invalid HTML and see what you get. You may need to experiment a bit in order to find something that will work. There are multiple ways to do this. </P> <P></P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_stored_sol');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="xss_stored_sol" style="display:none"> <P> <B>To exploit,</B> enter any of these as your snippet (there are certainly more methods): </P> <P></P> <PRE> (1) <a onmouseover="alert(1)" href="#">read this!</a> (2) <p <script>alert(1)</script>hello (3) </td <script>alert(1)</script>hello </PRE> <P> Notice that there are multiple failures in sanitizing the HTML. Snippet 1 worked because <CODE>onmouseover</CODE> was inadvertently omitted from the list of disallowed attributes in <CODE><A href="/code/?sanitize.py">sanitize.py</A></CODE>. Snippets 2 and 3 work because browsers tend to be forgiving with HTML syntax and the handling of both start and end tags is buggy. </P> <P> <B>To fix,</B> we need to investigate and fix the sanitizing performed on the snippets. Snippets are sanitized in <CODE>_SanitizeTag</CODE> in the <CODE><A href="/code/?sanitize.py">sanitize.py</A></CODE> file. Let's block snippet 1 by adding <CODE>"onmouseover"</CODE> to the list of <CODE>disallowed_attributes</CODE>. </P> <P> <B>Oops!</B> This doesn't completely solve the problem. Looking at the code that was just fixed, can you find a way to bypass the fix? </P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_stored_not_fixed_hint');"><IMG src="/static/closed.gif"> Hint </H4> <DIV id="xss_stored_not_fixed_hint" style="display:none"> <P> Take a close look at the code in <CODE>_SanitizeTag</CODE> that determines whether or not an HTML attribute is allowed or not. </P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_stored_not_fixed');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="xss_stored_not_fixed" style="display:none"> <P> The fix was insufficient because the code that checks for disallowed attributes is case sensitive and HTML is not. So this still works: </P> <P></P> <PRE> (1') <a ONMOUSEOVER="alert(1)" href="#">read this!</a> </PRE> <P> Correctly sanitizing HTML is a tricky problem. The <CODE>_SanitizeTag</CODE> function has a number of critical design flaws: </P> <P></P> <UL> <LI> It does not validate the well-formedness of the input HTML. As we see, badly formed HTML passes through the sanitizer unchanged. Since browsers typically apply very lenient parsing, it is very hard to predict the browser's interpretation of the given HTML unless we exercise strict control on its format. </LI> <LI> It uses blacklisting of attributes, which is a bad technique. One of our exploits got past the blacklist simply by using an uppercase version of the attribute. There could be other attributes <A href="https://www.w3.org/TR/html40/index/attributes.html" target="_top">missing from this list</A> that are dangerous. It is always better to whitelist known good values. </LI> <LI> The sanitizer does not do any further sanitization of attribute values. This is dangerous since URI attributes like <CODE>href</CODE> and <CODE>src</CODE> and the <CODE>style</CODE> attribute can all be used to inject JavaScript. </LI> </UL> <P> The right approach to HTML sanitization is to: </P><UL> <LI> Parse the input into an intermediate DOM structure, then rebuild the body as well-formed output. </LI> <LI> Use strict whitelists for allowed tags and attributes. </LI> <LI> Apply strict sanitization of URL and CSS attributes if they are permitted. </LI> </UL> <P>Whenever possible it is preferable to use an already available known and proven <A href="https://www.google.com/search?q=sanitize+html" target="_top">HTML sanitizer</A>. </P> <!--MARK-3--> <P></P> </DIV> </DIV> </DIV> <P></P> <H3><A name="2__stored_xss_via_html_attribute"> </A> Stored XSS via HTML Attribute </H3> <P> <IMG src="/static/cheese_b.png" style="vertical-align:middle; padding-right: 5px;"> <B>You can also do XSS by injecting a value into an HTML attribute. Inject a script by setting the color value in a profile.</B> </P><DIV style="margin-left:18pt"> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_color_hint');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="xss_color_hint" style="display:none"> <P> The color is rendered as <CODE>style='color:<I>color</I>'</CODE>. Try including a single quote character in your color name. </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_color_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="xss_color_hint2" style="display:none"> <P> You can insert an HTML attribute that executes a script. </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_color_sol');"><IMG src="/static/closed.gif"> Exploit and Fixes </H4> <DIV id="xss_color_sol" style="display:none"> <P> <B>To exploit,</B> use the following for your color preference: </P> <P></P> <PRE> red' onload='alert(1)' onmouseover='alert(2) </PRE> <P> You may need to move the mouse over the snippet to trigger the attack. This attack works because the first quote ends the <CODE>style</CODE> attribute and the second quote starts the onload attribute. </P> <P> But this attack shouldn't work at all. Take a look at <CODE><A href="/code/?resources/home.gtl">home.gtl</A></CODE> where it renders the color. It says <CODE>style='{{color:text}}'</CODE> and as we saw earlier, the <CODE>:text</CODE> part tells it to escape text. So why doesn't this get escaped? In <CODE><A href="/code/?gtl.py">gtl.py</A></CODE>, it calls <CODE>cgi.escape(str(value))</CODE> which takes an optional second parameter that indicates that the value is being used in an HTML attribute. So you can replace this with <CODE>cgi.escape(str(value),True)</CODE>. Except that doesn't fix it! The problem is that <CODE>cgi.escape</CODE> assumes your HTML attributes are enclosed in double quotes and this file is using single quotes. (This should teach you to always carefully read the documentation for libraries you use and to always test that they do what you want.) </P> <P> You'll note that this attack uses both <CODE>onload</CODE> and <CODE>onmouseover</CODE>. That's because even though W3C specifies that onload events is only supported on <CODE>body</CODE> and <CODE>frameset</CODE> elements, some browsers support them on other elements. So if the victim is using one of those browsers, the attack always succeeds. Otherwise, it succeeds when the user moves the mouse. It's not uncommon for attackers to use multiple attack vectors at the same time. </P> <P> <B>To fix,</B> we need to use a correct text escaper, that escapes single and double quotes too. Add the following function to <CODE><A href="/code/?gtl.py">gtl.py</A></CODE> and call it instead of <CODE>cgi.escape</CODE> for the <CODE>text</CODE> escaper. </P> <P></P> <PRE> def _EscapeTextToHtml(var): """Escape HTML metacharacters. This function escapes characters that are dangerous to insert into HTML. It prevents XSS via quotes or script injected in attribute values. It is safer than cgi.escape, which escapes only <, >, & by default. cgi.escape can be told to escape double quotes, but it will never escape single quotes. """ meta_chars = { '"': '&quot;', '\'': '&#39;', # Not &apos; '&': '&amp;', '<': '&lt;', '>': '&gt;', } escaped_var = "" for i in var: if i in meta_chars: escaped_var = escaped_var + meta_chars[i] else: escaped_var = escaped_var + i return escaped_var </PRE> <P> <B>Oops!</B> This doesn't completely solve the problem. Even with the above fix in place, the color value is still vulnerable. <H4 style="cursor: pointer" onclick="toggleBlock(this, 'another_style_xss_hint1');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="another_style_xss_hint1" style="display:none"> <P> Some browsers allow you to include script in stylesheets. </P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'another_style_xss_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="another_style_xss_hint2" style="display:none"> <P> The easiest browser to exploit in this way is Internet Explorer which supports dynamic CSS properties. </P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'another_style_xss_sol');"><IMG src="/static/closed.gif"> Another Exploit and Fix </H4> <DIV id="another_style_xss_sol" style="display:none"> <P> Internet Explorer's dynamic CSS properites (aka CSS expressions) make this attack particularly easy. </P> <B>To exploit,</B> use the following for your color preference: <PRE> expression(alert(1)) </PRE> <P> While other browsers don't support CSS expressions, there are other dangerous CSS properties, such as Mozilla's <CODE>-moz-binding</CODE>. </P> <P> <B>To fix,</B> we need to sanitize the color as a color. The best thing to do would be to add a new output sanitizing form to gtl, i.e., we would write <CODE>{{foo:color}}</CODE> which makes sure <CODE>foo</CODE> is safe to use as a color. This function can be used to sanitize: </P> <PRE> SAFE_COLOR_RE = re.compile(r"^#?[a-zA-Z0-9]*$") def _SanitizeColor(color): """Sanitizes a color, returning 'invalid' if it's invalid. A valid value is either the name of a color or # followed by the hex code for a color (like #FEFFFF). Returning an invalid value value allows a style sheet to specify a default value by writing 'color:default; color:{{foo:color}}'. """ if SAFE_COLOR_RE.match(color): return color return 'invalid' </PRE> <P> Colors aren't the only values we might want to allow users to provide. You should do similar sanitizing for user-provided fonts, sizes, urls, etc. It's helpful to do input validation, so that when a user enters an invalid value, you'll reject it at that time. But only doing input validation would be a mistake: if you find an error in your validation code or a new browser exposes a new attack vector, you'd have to go back and scrub all previously entered values. Or, you could add the output validation which you should have been doing in the first place. </DIV> </P></DIV> </DIV> <P></P> <H3><A name="2__stored_xss_via_ajax"> </A> Stored XSS via AJAX </H3> <P> <IMG src="/static/cheese_b.png" style="vertical-align:middle; padding-right: 5px;"> <B>Find an XSS attack that uses a bug in Gruyere's AJAX code.</B> The attack should be triggered when you click the refresh link on the page. </P> <P></P> <DIV style="margin-left:18pt"> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_ajax_hint');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="xss_ajax_hint" style="display:none"> <P> Run <CODE>curl</CODE> on <CODE>https://google-gruyere.appspot.com/382665580745386307547168512335551204731/feed.gtl</CODE> and look at the result. (Or browse to it in your browser and view source.) You'll see that it includes each user's first snippet into the response. This entire response is then evaluated on the client side which then inserts the snippets into the document. Can you put something in your snippet that will be parsed differently than expected? </P> <P></P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_ajax_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="xss_ajax_hint2" style="display:none"> <P> Try putting some quotes (<CODE>"</CODE>) in your snippet. </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_ajax_sol');"><IMG src="/static/closed.gif"> Exploit and Fixes </H4> <DIV id="xss_ajax_sol" style="display:none"> <P> <B>To exploit,</B> Put this in your snippet: </P> <P></P> <PRE> all <span style=display:none>" + (alert(1),"") + "</span>your base </PRE> <P> The JSON should look like <PRE>_feed(({..., "Mallory": "snippet", ...}))</PRE> but instead looks like this: <PRE>_feed({..., "Mallory": <U>"all <span style=display:none>"</U> + <U>(alert(1),"")</U> + <U>"</span>your base"</U>, ...})</PRE> Each underlined part is a separate expression. Note that this exploit is written to be invisible both in the original page rendering (because of the <CODE><span style=display:none></CODE>) and after refresh (because it inserts only an empty string). All that will appear on the screen is <A href="https://www.google.com/search?q=all+your+base+are+belong+to+us" target="_top">all your base</A>. There are bugs on both the server and client sides which enable this attack. </P> <P> <B>To fix,</B> first, on the server side, the text is incorrectly escaped when it is rendered in the JSON response. The template says <CODE>{{snippet.0:html}}</CODE> but that's not enough. This text is going to be inserted into the innerHTML of a DOM node so the HTML does have to be sanitized. However, that sanitized text is then going to be inserted into JavaScript and single and double quotes have to be escaped. That is, adding support for <CODE>{{...:js}}</CODE> to GTL would not be sufficient; we would also need to support something like <CODE>{{...:html:js}}</CODE>. </P><P>To escape quotes, use <CODE>\x27</CODE> and <CODE>\x22</CODE> for single and double quote respectively. Replacing them with <CODE>&#27;</CODE> and <CODE>&quot;</CODE> is incorrect as those are not recognized in JavaScript strings and will break quotes around HTML attribute. </P> <P> Second, in the browser, Gruyere converts the JSON by using JavaScript's <CODE>eval</CODE>. In general, <CODE>eval</CODE> is very dangerous and should rarely be used. If it used, it must be used very carefully, which is hardly the case here. We should be using the JSON parser which ensures that the string does not include any unsafe content. The JSON parser is available at <A href="http://www.json.org/" target="_top">json.org</A>. </P> <P></P> </DIV> </DIV> <P></P> <H3><A name="2__reflected_xss_via_ajax"> </A> Reflected XSS via AJAX </H3> <P> <IMG src="/static/cheese_b.png" style="vertical-align:middle; padding-right: 5px;"><B>Find a URL that when clicked on will execute a script using one of Gruyere's AJAX features.</B> </P> <P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_ajax2_hint');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="xss_ajax2_hint" style="display:none"> <P> When Gruyere refreshes a user snippets page, it uses <PRE>https://google-gruyere.appspot.com/382665580745386307547168512335551204731/feed.gtl?uid=value</PRE> and the result is the script <PRE>_feed((["user", "snippet1", ... ]))</PRE> </P> <P></P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_ajax2_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="xss_ajax2_hint2" style="display:none"> <P> This uses a different vulnerability, but the exploit is very similar to the previous reflected XSS exploit. </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xss_ajax2_sol');"><IMG src="/static/closed.gif"> Exploit and Fixes </H4> <DIV id="xss_ajax2_sol" style="display:none"> <P> <B>To exploit,</B> create a URL like the following and get a victim to click on it: </P> <P></P> <PRE> https://google-gruyere.appspot.com/382665580745386307547168512335551204731/feed.gtl?uid=<script>alert(1)</script> https://google-gruyere.appspot.com/382665580745386307547168512335551204731/feed.gtl?uid=%3Cscript%3Ealert(1)%3C/script%3E </PRE> <P> This renders as <PRE>_feed((["<script>alert(1)</script>"]))</PRE> which surprisingly <I>does</I> execute the script. The bug is that Gruyere returns all gtl files as content type <CODE>text/html</CODE> and browsers are very tolerant of what HTML files they accept. </P> <P> <B>To fix,</B> you need to make sure that your JSON content can never be interpreted as HTML. Even though literal <CODE><</CODE> and <CODE>></CODE> are allowed in JavaScript strings, you need to make sure they don't appear literally where a browser can misinterpret them. Thus, you'd need to modify <CODE>{{...:js}}</CODE> to replace them with the JavaScript escapes <CODE>\x3c</CODE> and <CODE>\x3e</CODE>. It is always safe to write <CODE>'\x3c\x3e'</CODE> in Javscript strings instead of <CODE>'<>'</CODE>. (And, as noted above, using the HTML escapes <CODE>&lt;</CODE> and <CODE>&gt;</CODE> is incorrect.) </P> <P> You should also always set the content type of your responses, in this case serving JSON results as <CODE>application/javascript.</CODE> This alone doesn't solve the problem because browsers don't always respect the content type: browsers sometimes do "sniffing" to try to "fix" results from servers that don't provide the correct content type. </P> <P><B>But wait, there's more!</B> Gruyere doesn't set the content encoding either. And some browsers try to guess what the encoding type of a document is or an attacker may be able to embed content in a document that defines the content type. So, for example, if an attacker can trick the browser into thinking a document is <CODE><A href="https://www.google.com/search?q=utf-7">UTF-7</A></CODE> then it could embed a script tag as <CODE>+ADw-script+AD4-</CODE> since <CODE>+ADw-</CODE> and <CODE>+AD4-</CODE> are alternate encodings for <CODE><</CODE> and <CODE>></CODE>. So always set both the content type <I>and</I> the content encoding of your responses, e.g., for HTML:</P> <PRE> Content-Type: text/html; charset=utf-8 </PRE> </DIV> </DIV> <H3><A name="2__more_about_xss"> </A> More about XSS </H3> <P> In addition to the XSS attacks described above, there are quite a few more ways to attack Gruyere with XSS. Collect them all! </P> <P> XSS is a difficult beast. On one hand, a fix to an XSS vulnerability is usually trivial and involves applying the correct sanitizing function to user input when it's displayed in a certain context. On the other hand, if history is any indication, this is extremely difficult to get right. <A href="https://www.kb.cert.org/vuls/" target="_top">US-CERT</A> reports dozens of publicly disclosed XSS vulnerabilities involving multiple companies. </P> <P> Though there is no magic defense to getting rid of XSS vulnerabilities, here are some steps you should take to prevent these types of bugs from popping up in your products: </P> <P></P> <OL> <LI> First, make sure you <A href="https://www.google.com/search?q=understanding+cross-site+scripting" target="_top">understand the problem</A>. </LI> <LI> Wherever possible, do sanitizing via templates features instead of calling escaping functions in source code. This way, all of your escaping is done in one place and your product can benefit from security technologies designed for template systems that verify their correctness or actually do the escaping for you. Also, familiarize yourself with the other security features of your template system. </LI> <LI> Employ good testing practices with respect to XSS. </LI> <LI> Don't write your own template library :) </LI> </OL> <!--MARK-6--> <BR><P></P> <FONT SIZE="+2"> <A href="/part3">Continue >></A> </FONT><BR> <BR> <P style="font-size:x-small"> © Google 2017 <A href="https://www.google.com/intl/en/policies/terms/">Terms of Service</A> <BR>The code portions of this codelab are licensed under the Creative Commons Attribution-No Derivative Works 3.0 United States license <<A href="https://creativecommons.org/licenses/by-nd/3.0/us/">https://creativecommons.org/licenses/by-nd/3.0/us</A>>. Brief excerpts of the code may be used for educational or instructional purposes provided this notice is kept intact. Except as otherwise noted the remainder of this codelab is licensed under the Creative Commons Attribution 3.0 United States license <<A href="https://creativecommons.org/licenses/by/3.0/us/">https://creativecommons.org/licenses/by/3.0/us</A>>. </P> </BODY></HTML> |
| URL | https://google-gruyere.appspot.com/part3 |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 365 bytes. |
GET https://google-gruyere.appspot.com/part3 HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: https://google-gruyere.appspot.com/ Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 301 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: f4439e90565e10881aa7a1deee88e28a Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 29253 Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 |
| Response Body - size: 29,253 bytes. |
<HTML><HEAD><META http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<TITLE>Web Application Exploits and Defenses</TITLE> <LINK type="text/css" rel="stylesheet" href="../static/codelab.css"> <!--MARK-Z--> <SCRIPT> function toggleBlock(heading, whichID) { var image = heading.childNodes[0]; var block = document.getElementById(whichID); if (block) { if (getDisplay(block) == 'block') { block.style.display = 'none'; image.src = 'static/closed.gif'; } else { // "none" or "" block.style.display = 'block'; image.src = 'static/open.gif'; } } } function getDisplay(block) { var value = block.style.display; if (!value) { if (document.defaultView) { var computedStyle = document.defaultView.getComputedStyle(block, ""); value = computedStyle.getPropertyValue('display'); } else if (block.currentStyle) { value = block.currentStyle.display; } } return value; } </SCRIPT> </HEAD> <BODY bgcolor="#ffffff"> <DIV class="banner"></DIV> <H1><FONT size="+2"><IMG src="/static/gruyere-78.png" style="vertical-align:middle"> Web Application Exploits and Defenses (Part 3)</FONT> </H1> <P> A Codelab by Bruce Leban, Mugdha Bendre, and Parisa Tabriz </P> <DIV class="printable"> <DIV class="column1"> Table of Contents <UL> <LI class="L1" style="padding-top:0pt"> <A href="/#0__hackers">Beat the hackers</A></LI> <LI class="L1" style="padding-top:0pt"> <A href="/#0__gruyere">Gruyere</A></LI> <LI class="L1"> <A href="/part1#1__setup">Set-up</A> <UL> <LI class="L2"> <A href="/part1#1__reset_button">Reset Button</A></LI> <LI class="L2"> <A href="/part1#1__about_the_code">About the Code</A></LI> <LI class="L2"> <A href="/part1#1__features_and_technologies">Features and Technologies</A></LI> </UL></LI> <LI class="L1"> <A href="/part1#1__using_gruyere">Using Gruyere</A></LI> <LI class="L1"> <A href="/part2#2__cross_site_scripting">Cross-Site Scripting (XSS)</A> <UL> <LI class="L2"> <A href="/part2#2__xss_challenge">XSS Challenges</A></LI> <LI class="L2"> <A href="/part2#2__file_upload_xss">File Upload XSS</A></LI> <LI class="L2"> <A href="/part2#2__reflected_xss">Reflected XSS</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss">Stored XSS</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss_via_html_attribute">Stored XSS via HTML Attribute</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss_via_ajax">Stored XSS via AJAX</A></LI> <LI class="L2"> <A href="/part2#2__reflected_xss_via_ajax">Reflected XSS via AJAX</A></LI> <LI class="L2"> <A href="/part2#2__more_about_xss">More about XSS</A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__client_state_manipulation">Client-State Manipulation</A> <UL> <LI class="L2"> <A href="/part3#3__elevation_of_privilege"> Elevation of Privilege </A></LI> <LI class="L2"> <A href="/part3#3__cookie_manipulation"> Cookie Manipulation </A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__cross_site_request_forgery">Cross-Site Request Forgery (XSRF)</A> <UL> <LI class="L2"> <A href="/part3#3__xsrf_challenge">XSRF Challenge</A></LI> <LI class="L2"> <A href="/part3#3__more_about_preventing_xsrf">More about preventing XSRF</A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__cross_site_script_inclusion">Cross Site Script Inclusion (XSSI)</A> <UL> <LI class="L2"> <A href="/part3#3__xssi_challenge">XSSI Challenge</A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__path_traversal">Path Traversal</A> <UL> <LI class="L2"> <A href="/part4#4__information_disclosure_path_traversal">Information disclosure via path traversal </A></LI> <LI class="L2"> <A href="/part4#4__data_tampering_path_traversal">Data tampering via path traversal </A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__denial_of_service">Denial of Service</A> <UL> <LI class="L2"> <A href="/part4#4__dos_quit_server">DoS - Quit the Server</A></LI> <LI class="L2"> <A href="/part4#4__dos_overload_server">DoS - Overloading the Server</A></LI> <LI class="L2"> <A href="/part4#4__more_dos">More on Denial of Service</A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__code_execution">Code Execution</A> <UL> <LI class="L2"> <A href="/part4#4__code_execution_challenge">Code Execution Challenge</A></LI> <LI class="L2"> <A href="/part4#4__more_code_execution">More on Remote Code Execution</A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__configuration_vulnerabilities">Configuration Vulnerabilities</A> <UL> <LI class="L2"> <A href="/part5#5__information_disclosure_config_1">Information disclosure #1</A></LI> <LI class="L2"> <A href="/part5#5__information_disclosure_config_2">Information disclosure #2</A></LI> <LI class="L2"> <A href="/part5#5__information_disclosure_bug_3">Information disclosure #3 </A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__ajax_vulnerabilities">AJAX vulnerabilities</A> <UL> <LI class="L2"> <A href="/part5#5__dos_via_ajax">DoS via AJAX</A></LI> <LI class="L2"> <A href="/part5#5__phishing_via_ajax">Phishing via AJAX</A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__other_vulnerabilities"> Other Vulnerabilities </A> </H2> <UL> <LI class="L2"> <A href="/part5#5__buffer_and_integer_overflow">Buffer Overflow and Integer Overflow</A></LI> <LI class="L2"> <A href="/part5#5__sql_injection"> SQL Injection </A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__after_the_codelab">After the Codelab</A></LI> </UL> </DIV> <DIV class="column2"> <P></P> <P></P> <P> <NOAUTOLINK> </NOAUTOLINK></P> <H2><A name="3__client_state_manipulation"> </A> Client-State Manipulation </H2> <P> When a user interacts with a web application, they do it indirectly through a browser. When the user clicks a button or submits a form, the browser sends a request back to the web server. Because the browser runs on a machine that can be controlled by an attacker, the application must not trust any data sent by the browser. </P><P> It might seem that not trusting any user data would make it impossible to write a web application but that's not the case. If the user submits a form that says they wish to purchase an item, it's OK to trust that data. But if the submitted form also includes the price of the item, that's something that cannot be trusted. </P> <P></P> <H3><A name="3__elevation_of_privilege"> </A> Elevation of Privilege </H3> <IMG src="/static/cheese_w.png" style="vertical-align:middle; padding-right: 5px;"> <B>Convert your account to an administrator account.</B> <P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'elevation_hint');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="elevation_hint" style="display:none"> <P> Take a look at the <CODE><A href="/code/?resources/editprofile.gtl">editprofile.gtl</A></CODE> page that users and administrators use to edit profile settings. If you're not an administrator, the page looks a bit different. Can you figure out how to fool Gruyere into letting you use this page to update your account? </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'elevation_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="elevation_hint2" style="display:none"> <P> Can you figure out how to fool Gruyere into <i>thinking</i> you used this page to update your account? </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'clientstate');"><IMG src="/static/closed.gif"> Exploit and Fixes </H4> <DIV id="clientstate" style="display:none"> <P> You can convert your account to being an administrator by issuing either of the following requests: </P><UL> <LI> <CODE>https://google-gruyere.appspot.com/382665580745386307547168512335551204731/saveprofile?action=update&is_admin=True</CODE> </LI> <LI> <CODE>https://google-gruyere.appspot.com/382665580745386307547168512335551204731/saveprofile?action=update&is_admin=True&uid=username</CODE> (which will make any <CODE>username</CODE> into an an admin) </LI> </UL> <P>After visiting this URL, your account is now marked as an administrator but your cookie still says you're not. So sign out and back in to get a new cookie. After logging in, notice the 'Manage this server' link on the top right.</P> <P>The bug here is that there is no validation on the server side that the request is authorized. The only part of the code that restricts the changes that a user is allowed to make are in the template, hiding parts of the UI that they shouldn't have access to. The correct thing to do is to check for authorization on the server, at the time that the request is received. </P> </DIV> </DIV> <P></P> <H3><A name="3__cookie_manipulation"> </A> Cookie Manipulation </H3> Because the HTTP protocol is stateless, there's no way a web server can automatically know that two requests are from the same user. For this reason, <A href="https://www.google.com/search?q=http+cookies">cookies</a> were invented. When a web site includes a cookie (an arbitrary string) in a HTTP response, the browser automatically sends the cookie back to the browser on the next request. Web sites can use the cookie to save session state. Gruyere uses cookies to remember the identity of the logged in user. Since the cookie is stored on the client side, it's vulnerable to manipulation. Gruyere protects the cookies from manipulation by adding a hash to it. Notwithstanding the fact that this hash isn't very good protection, you don't need to break the hash to execute an attack. </P><P> <IMG src="/static/cheese_bw.png" style="vertical-align:middle; padding-right: 5px;"> <B>Get Gruyere to issue you a cookie for someone else's account.</B> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'cookie_hint');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="cookie_hint" style="display:none"> <P> You don't need to look at the Gruyere cookie parsing code. You just need to know what the cookies look like. Gruyere's cookies use the format: <PRE> <i>hash</i>|<i>username</i>|admin|author </PRE> </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'cookie_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="cookie_hint2" style="display:none"> <P> Gruyere issues a cookie when you log in. Can you trick it into issuing you a cookie that looks like another user's cookie? </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'cookieparsing');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="cookieparsing" style="display:none"> <P> You can get Gruyere to issue you a cookie for someone else's account by creating a new account with username <CODE>"foo|admin|author"</CODE>. When you log into this account, it will issue you the cookie <CODE>"hash|foo|admin|author||author"</CODE> which actually logs you into <CODE>foo</CODE> as an administrator. (So this is also an elevation of privilege attack.) </P> <P>Having no restrictions on the characters allowed in usernames means that we have to be careful when we handle them. In this case, the cookie parsing code is tolerant of malformed cookies and it shouldn't be. It should escape the username when it constructs the cookie and it should reject a cookie if it doesn't match the exact pattern it is expecting. </P> <P>Even if we fix this, Python's hash function is not cryptographically secure. If you look at Python's <CODE>string_hash</CODE> function in <CODE><a href="https://svn.python.org/projects/python/trunk/Objects/stringobject.c">python/Objects/stringobject.cc</A></CODE> you'll see that it hashes the string strictly from left to right. That means that we don't need to know the cookie secret to generate our own hashes; all we need is another string that hashes to the same value, which we can find in a relatively short time on a typical PC. In contrast, with a cryptographic hash function, changing any bit of the string will change many bits of the hash value in an unpredictable way. At a minimum, you should use a secure hash function to protect your cookies. You should also consider encrypting the entire cookie as plain text cookies can expose information you might not want exposed. </P> <P>And these cookies are also vulnerable to a replay attack. Once a user is issued a cookie, it's good forever and there's no way to revoke it. So if a user is an administrator at one time, they can save the cookie and continue to act as an administrator even if their administrative rights are taken away. While it's convenient to not have to make a database query in order to check whether or not a user is an administrator, that might be too dangerous a detail to store in the cookie. If avoiding additional database access is important, the server could cache a list of recent admin users. Including a timestamp in a cookie and expiring it after some period of time also mitigates against a replay attack.</P> <P><B>Another challenge:</B> Since account names are limited to 16 characters, it seems that this trick would not work to log in to the actual <CODE>administrator</CODE> account since <CODE>"administrator|admin"</CODE> is 19 characters. Can you figure out how to bypass that restriction? </P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'cookie2');"><IMG src="/static/closed.gif"> Additional Exploit and Fix </H4> <DIV id="cookie2" style="display:none"> The 16 character limit is implemented on the client side. Just issue your own request: <PRE> https://google-gruyere.appspot.com/382665580745386307547168512335551204731/saveprofile?action=new&uid=administrator|admin|author&pw=secret </PRE> <P>Again, this restriction should be implemented on the server side, not just the client side.</P> </DIV> </DIV> </DIV> <BR><BR> <H2><A name="3__cross_site_request_forgery"> </A> Cross-Site Request Forgery (XSRF) </H2> <P> The previous section said "If the user submits a form that says they wish to purchase an item, it's OK to trust that data." That's true as long as it really was the user that submitted the form. If your site is vulnerable to XSS, then the attacker can fake any request as if it came from the user. But even if you've protected against XSS, there's another attack that you need to protect against: cross-site request forgery. </P><P> When a browser makes requests to a site, it always sends along any cookies it has for that site, regardless of where the request comes from. Additionally, web servers generally cannot distinguish between a request initiated by a deliberate user action (e.g., user clicking on "Submit" button) versus a request made by the browser without user action (e.g., request for an embedded image in a page). Therefore, if a site receives a request to perform some action (like deleting a mail, changing contact address), it cannot know whether this action was knowingly initiated by the user — even if the request contains authentication cookies. An attacker can use this fact to fool the server into performing actions the user did not intend to perform. </P> <P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xsrf_desc');"><IMG src="/static/closed.gif"> More details </H4> <DIV id="xsrf_desc" style="display:none"> <P> For example, suppose Blogger is vulnerable to XSRF attacks (it isn't). And let us say Blogger has a Delete Blog button on the dashboard that points to this URL: <PRE> https://www.blogger.com/deleteblog.do?blogId=BLOGID </PRE> Bob, the attacker, embeds the following HTML on his web page on <CODE>https://www.evil.example.com</CODE>: </P> <PRE> <img src="https://www.blogger.com/deleteblog.do?blogId=alice's-blog-id" style="display:none"> </PRE> <P> If the victim, Alice, is logged in to <CODE>www.blogger.com</CODE> when she views the above page, here is what happens: </P> <P></P> <UL> <LI> Her browser loads the page from <CODE>https://www.evil.example.com</CODE>. The browser then tries to load all embedded objects in the page, including the <CODE>img</CODE> shown above. </LI> <LI> The browser makes a request to <CODE>https://www.blogger.com/deleteblog.do?blogId=alice's-blog-id</CODE> to load the image. Since Alice is logged into Blogger — that is, she has a Blogger cookie — the browser also sends that cookie in the request. </LI> <LI> Blogger verifies the cookie is a valid session cookie for Alice. It verifies that the blog referenced by <CODE>alice's-blog-id</CODE> is owned by Alice. It deletes Alice's blog. </LI> <LI> Alice has no idea what hit her. </LI> </UL> <P> In this sample attack, since each user has their own blog id, the attack has to be specifically targeted to a single person. In many cases, though, requests like these don't contain any user-specific data. </P></DIV> </DIV> <P></P> <H3><A name="3__xsrf_challenge"> </A> XSRF Challenge </H3> <P> The goal here is to find a way to perform an account changing action on behalf of a logged in Gruyere user without their knowledge. Assume you can get them to visit a web page under your control. </P> <P> <IMG src="/static/cheese_b.png" style="vertical-align:middle; padding-right: 5px;"> <B> Find a way to get someone to delete one of their Gruyere snippets.</B> </P> <P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xsrf_hint');"><IMG src="/static/closed.gif"> Hint </H4> <DIV id="xsrf_hint" style="display:none"> <P> What is the URL used to delete a snippet? Look at the URL associated with the "X" next to a snippet. </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xsrf_soln');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="xsrf_soln" style="display:none"> <P> <B>To exploit,</B> lure a user to visit a page that makes the following request: </P> <P></P> <PRE> https://google-gruyere.appspot.com/382665580745386307547168512335551204731/deletesnippet?index=0 </PRE> <P> To be especially sneaky, you could set your Gruyere icon to this URL and the victim would be exploited when they visited the main page. </P> <P> <B>To fix,</B> we should first change <CODE>/deletesnippet</CODE> to work via a <CODE>POST</CODE> request since this is a state changing action. In the HTML form, change <CODE>method='get'</CODE> to <CODE>method='post'</CODE>. On the server side, <CODE>GET</CODE> and <CODE>POST</CODE> requests look the same except that they usually call different handlers. For example, Gruyere uses Python's BaseHTTPServer which calls <CODE>do_GET</CODE> for <CODE>GET</CODE> requests and <CODE>do_POST</CODE> for <CODE>POST</CODE> requests. </P><P> <B>However</B>, note that changing to <CODE>POST</CODE> is not enough of a fix in itself! (Gruyere uses <CODE>GET</CODE> requests exclusively because it makes hacking it a bit easier. <CODE>POST</CODE> is not more secure than <CODE>GET</CODE> but it is more correct: browsers may re-issue <CODE>GET</CODE> requests which can result in an action getting executed more than once; browsers won't reissue <CODE>POST</CODE> requests without user consent.) Then we need to pass a unique, unpredictable authorization token to the user and require that it get sent back before performing the action. For this authorization token, <CODE>action_token</CODE>, we can use a hash of the value of the user's cookie appended to a current timestamp and include this token in all state-changing HTTP requests as an additional HTTP parameter. The reason we use <CODE>POST</CODE> over <CODE>GET</CODE> requests is that if we pass <CODE>action_token</CODE> as a URL parameter, it might leak via HTTP Referer headers. The reason we include the timestamp in our hash is so that we can expire old tokens, which mitigates the risk if it leaks. </P> <P> When a request is processed, Gruyere should regenerate the token and compare it with the value supplied with the request. If the values are equal, then it should perform the action. Otherwise, it should reject it. The functions that generate and verify the tokens look like this: </P> <P></P> <PRE> def _GenerateXsrfToken(self, cookie): """Generates a timestamp and XSRF token for all state changing actions.""" timestamp = time.time() return timestamp + "|" + (str(hash(cookie_secret + cookie + timestamp))) def _VerifyXsrfToken(self, cookie, action_token): """Verifies an XSRF token included in a request.""" # First, make sure that the token isn't more than a day old. (action_time, action_hash) = action_token.split("|", 1) now = time.time() if now - 86400 > float(action_time): return False # Second, regenerate it and check that it matches the user supplied value hash_to_verify = str(hash(cookie_secret + cookie + action_time) return action_hash == hash_to_verify </PRE> <P> <B>Oops!</B> There's several things wrong with these functions. </P><H4 style="cursor: pointer" onclick="toggleBlock(this, 'xsrf_whats_wrong');"><IMG src="/static/closed.gif"> What's missing? </H4> <DIV id="xsrf_whats_wrong" style="display:none"> <P> By including the time in the token, we prevent it from being used forever, but if an attacker were to gain access to a copy of the token, they could reuse it as many times as they wanted within that 24 hour period. The expiration time of a token should be set to a small value that represents the reasonable length of time it will take the user to make a request. This token also doesn't protect against an attack where a token for one request is intercepted and then used for a different request. As suggested by the name <CODE>action_token</CODE>, the token should be tied to the specific state changing action being performed, such as the URL of the page. A better signature for <CODE>_GenerateXsrfToken</CODE> would be <CODE>(self, cookie, action)</CODE>. For very long actions, like editing snippets, a script on the page could query the server to update the token when the user hits submit. (But read the next section about XSSI to make sure that an attacker won't be able to read that new token.)</P> <P> XSRF vulnerabilities exist because an attacker can easily script a series of requests to an application and than force a user to execute them by visiting some page. To prevent this type of attack, you need to introduce some value that can't be predicted or scripted by an attacker for <B>every account changing</B> request. Some application frameworks have XSRF protection built in: they automatically include a unique token in every response and verify it on every POST request. Other frameworks provide functions that you can use to do that. If neither of these cases apply, then you'll have to <A href="https://www.google.com/search?q=preventing+(XSRF+OR+CSRF)" target="_top">build your own</A>. Be careful of things that don't work: using <CODE>POST</CODE> instead of <CODE>GET</CODE> is advisable but not sufficient by itself, checking Referer headers is insufficient, and copying cookies into hidden form fields can make your cookies less secure. </P> <!--MARK-7--> </DIV> </DIV> <BR><P></P> </DIV> <H2><A name="3__cross_site_script_inclusion"> </A> Cross Site Script Inclusion (XSSI) </H2> <P> Browsers prevent pages of one domain from reading pages in other domains. But they do not prevent pages of a domain from referencing resources in other domains. In particular, they allow images to be rendered from other domains and scripts to be executed from other domains. An included script doesn't have its own security context. It runs in the security context of the page that included it. For example, if <CODE>www.evil.example.com</CODE> includes a script hosted on <CODE>www.google.com</CODE> then that script runs in the <CODE>evil</CODE> context not in the <CODE>google</CODE> context. So any user data in that script will "leak." </P> <!--MARK-8--> <P></P> <H3><A name="3__xssi_challenge"> </A> XSSI Challenge </H3> <P> <IMG src="/static/cheese_w.png" style="vertical-align:middle; padding-right: 5px;"> <B>Find a way to read someone else's private snippet using XSSI.</B> </P> <P> That is, create a page on another web site and put something in that page that can read your private snippet. (You don't need to post it to a web site: you can just create a <CODE>.html</CODE> in your home directory and double click on it to open in a browser.) </P> <P></P> <DIV style="margin-left:18pt"> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xssi_hint1');"><IMG src="/static/closed.gif"> Hint 1 </H4> <P></P> <DIV id="xssi_hint1" style="display:none"> <P> You can run a script from another domain by adding <PRE> <SCRIPT src="https://google-gruyere.appspot.com/382665580745386307547168512335551204731/..."></SCRIPT> </PRE> to your HTML file. What scripts does Gruyere have? </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xssi_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="xssi_hint2" style="display:none"> <P> <CODE><A href="/code/?resources/feed.gtl">feed.gtl</A></CODE> is a script. Given that, how can you get the private snippet out of the script? </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'xssi_sol');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="xssi_sol" style="display:none"> <P> <B>To exploit,</B> put this in an html file: </P> <P></P> <PRE> <script> function _feed(s) { alert("Your private snippet is: " + s['private_snippet']); } </script> <script src="https://google-gruyere.appspot.com/382665580745386307547168512335551204731/feed.gtl"></script> </PRE> <P> When the script in <CODE><A href="/code/?resources/feed.gtl">feed.gtl</A></CODE> is executed, it runs in the context of the attacker's web page and uses the <CODE>_feed</CODE> function which can do whatever it wants with the data, including sending it off to another web site. </P> <P> You might think that you can fix this by eliminating the function call and just having the bare expression. That way, when the script is executed by inclusion, the response will be evaluated and then discarded. That won't work because JavaScript allows you to do things like redefine default constructors. So when the object is evaluated, the hosting page's constructors are invoked, which can do whatever they want with the values. </P> <!--MARK-9--> <P> <B>To fix,</B> there are several changes you can make. Any one of these changes will prevent currently possible attacks, but if you add several layers of protection ("<a href="https://www.google.com/search?q=%22defense+in+depth%22+security">defense in depth</a>") you protect against the possibility that you get one of the protections wrong and also against future browser vulnerabilities. First, use an XSRF token as discussed earlier to make sure that JSON results containing confidential data are only returned to your own pages. Second, your JSON response pages should only support <CODE>POST</CODE> requests, which prevents the script from being loaded via a script tag. Third, you should make sure that the script is not executable. The standard way of doing this is to append some non-executable prefix to it, like <CODE>])}while(1);</x></CODE>. A script running in the same domain can read the contents of the response and strip out the prefix, but scripts running in other domains can't. </P> <P> NOTE: Making the script not executable is more subtle than it seems. It's possible that what makes a script executable may change in the future if new scripting features or languages are introduced. Some people suggest that you can protect the script by making it a comment by surrounding it with <CODE>/*</CODE> and <CODE>*/</CODE>, but that's not as simple as it might seem. (Hint: what if someone included <CODE>*/</CODE> in one of their snippets?) </P> <P> There's <A href="https://www.google.com/search?q=%22cross+site+script+inclusion%22" target="_top">much more to XSSI</A> than this. There's a variation of JSON called JSONP which you should avoid using because it allows script injection <I>by design</I>. And there's <A href="https://www.google.com/search?q=E4X+markup+security" target="_top">E4X</A> (Ecmascript for XML) which can result in your HTML file being parsed as a script. Surprisingly, one way to protect against E4X attacks is to put some invalid XML in your files, like the <CODE></x></CODE> above. </P> <P></P> </DIV> </DIV> <BR><P></P> <FONT SIZE="+2"> <A href="/part4">Continue >></A> </FONT><BR> <BR> <P style="font-size:x-small"> © Google 2017 <A href="https://www.google.com/intl/en/policies/terms/">Terms of Service</A> <BR>The code portions of this codelab are licensed under the Creative Commons Attribution-No Derivative Works 3.0 United States license <<A href="https://creativecommons.org/licenses/by-nd/3.0/us/">https://creativecommons.org/licenses/by-nd/3.0/us</A>>. Brief excerpts of the code may be used for educational or instructional purposes provided this notice is kept intact. Except as otherwise noted the remainder of this codelab is licensed under the Creative Commons Attribution 3.0 United States license <<A href="https://creativecommons.org/licenses/by/3.0/us/">https://creativecommons.org/licenses/by/3.0/us</A>>. </P> </BODY></HTML> |
| URL | https://google-gruyere.appspot.com/part4 |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 365 bytes. |
GET https://google-gruyere.appspot.com/part4 HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: https://google-gruyere.appspot.com/ Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 301 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: 63bda5a5dfca824c605721a0d3f9018f Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 24149 Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 |
| Response Body - size: 24,149 bytes. |
<HTML><HEAD><META http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<TITLE>Web Application Exploits and Defenses</TITLE> <LINK type="text/css" rel="stylesheet" href="../static/codelab.css"> <!--MARK-Z--> <SCRIPT> function toggleBlock(heading, whichID) { var image = heading.childNodes[0]; var block = document.getElementById(whichID); if (block) { if (getDisplay(block) == 'block') { block.style.display = 'none'; image.src = 'static/closed.gif'; } else { // "none" or "" block.style.display = 'block'; image.src = 'static/open.gif'; } } } function getDisplay(block) { var value = block.style.display; if (!value) { if (document.defaultView) { var computedStyle = document.defaultView.getComputedStyle(block, ""); value = computedStyle.getPropertyValue('display'); } else if (block.currentStyle) { value = block.currentStyle.display; } } return value; } </SCRIPT> </HEAD> <BODY bgcolor="#ffffff"> <DIV class="banner"></DIV> <H1><FONT size="+2"><IMG src="/static/gruyere-78.png" style="vertical-align:middle"> Web Application Exploits and Defenses (Part 4)</FONT> </H1> <P> A Codelab by Bruce Leban, Mugdha Bendre, and Parisa Tabriz </P> <DIV class="printable"> <DIV class="column1"> Table of Contents <UL> <LI class="L1" style="padding-top:0pt"> <A href="/#0__hackers">Beat the hackers</A></LI> <LI class="L1" style="padding-top:0pt"> <A href="/#0__gruyere">Gruyere</A></LI> <LI class="L1"> <A href="/part1#1__setup">Set-up</A> <UL> <LI class="L2"> <A href="/part1#1__reset_button">Reset Button</A></LI> <LI class="L2"> <A href="/part1#1__about_the_code">About the Code</A></LI> <LI class="L2"> <A href="/part1#1__features_and_technologies">Features and Technologies</A></LI> </UL></LI> <LI class="L1"> <A href="/part1#1__using_gruyere">Using Gruyere</A></LI> <LI class="L1"> <A href="/part2#2__cross_site_scripting">Cross-Site Scripting (XSS)</A> <UL> <LI class="L2"> <A href="/part2#2__xss_challenge">XSS Challenges</A></LI> <LI class="L2"> <A href="/part2#2__file_upload_xss">File Upload XSS</A></LI> <LI class="L2"> <A href="/part2#2__reflected_xss">Reflected XSS</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss">Stored XSS</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss_via_html_attribute">Stored XSS via HTML Attribute</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss_via_ajax">Stored XSS via AJAX</A></LI> <LI class="L2"> <A href="/part2#2__reflected_xss_via_ajax">Reflected XSS via AJAX</A></LI> <LI class="L2"> <A href="/part2#2__more_about_xss">More about XSS</A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__client_state_manipulation">Client-State Manipulation</A> <UL> <LI class="L2"> <A href="/part3#3__elevation_of_privilege"> Elevation of Privilege </A></LI> <LI class="L2"> <A href="/part3#3__cookie_manipulation"> Cookie Manipulation </A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__cross_site_request_forgery">Cross-Site Request Forgery (XSRF)</A> <UL> <LI class="L2"> <A href="/part3#3__xsrf_challenge">XSRF Challenge</A></LI> <LI class="L2"> <A href="/part3#3__more_about_preventing_xsrf">More about preventing XSRF</A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__cross_site_script_inclusion">Cross Site Script Inclusion (XSSI)</A> <UL> <LI class="L2"> <A href="/part3#3__xssi_challenge">XSSI Challenge</A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__path_traversal">Path Traversal</A> <UL> <LI class="L2"> <A href="/part4#4__information_disclosure_path_traversal">Information disclosure via path traversal </A></LI> <LI class="L2"> <A href="/part4#4__data_tampering_path_traversal">Data tampering via path traversal </A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__denial_of_service">Denial of Service</A> <UL> <LI class="L2"> <A href="/part4#4__dos_quit_server">DoS - Quit the Server</A></LI> <LI class="L2"> <A href="/part4#4__dos_overload_server">DoS - Overloading the Server</A></LI> <LI class="L2"> <A href="/part4#4__more_dos">More on Denial of Service</A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__code_execution">Code Execution</A> <UL> <LI class="L2"> <A href="/part4#4__code_execution_challenge">Code Execution Challenge</A></LI> <LI class="L2"> <A href="/part4#4__more_code_execution">More on Remote Code Execution</A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__configuration_vulnerabilities">Configuration Vulnerabilities</A> <UL> <LI class="L2"> <A href="/part5#5__information_disclosure_config_1">Information disclosure #1</A></LI> <LI class="L2"> <A href="/part5#5__information_disclosure_config_2">Information disclosure #2</A></LI> <LI class="L2"> <A href="/part5#5__information_disclosure_bug_3">Information disclosure #3 </A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__ajax_vulnerabilities">AJAX vulnerabilities</A> <UL> <LI class="L2"> <A href="/part5#5__dos_via_ajax">DoS via AJAX</A></LI> <LI class="L2"> <A href="/part5#5__phishing_via_ajax">Phishing via AJAX</A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__other_vulnerabilities"> Other Vulnerabilities </A> </H2> <UL> <LI class="L2"> <A href="/part5#5__buffer_and_integer_overflow">Buffer Overflow and Integer Overflow</A></LI> <LI class="L2"> <A href="/part5#5__sql_injection"> SQL Injection </A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__after_the_codelab">After the Codelab</A></LI> </UL> </DIV> <DIV class="column2"> <P></P> <P></P> <P> <NOAUTOLINK> </NOAUTOLINK></P> <H2><A name="4__path_traversal"> </A> Path Traversal </H2> <P> Most web applications serve static resources like images and CSS files. Frequently, applications simply serve all the files in a folder. If the application isn't careful, the user can use a path traversal attack to read files from other folders that they shouldn't have access to. For example, in both Windows and Linux, <CODE>..</CODE> represents the parent directory, so if you can inject <CODE>../</CODE> in a path you can "escape" to the parent directory. </P> <P> If an attacker knows the structure of your file system, then they can craft a URL that will traverse out of the installation directory to <CODE>/etc</CODE>. For example, if Picasa was vulnerable to path traversal (it isn't) and the Picasa servers use a Unix-like system, then the following would retrieve the password file: </P> <P></P> <PRE> https://www.picasa.com/../../../../../../../etc/passwd </PRE> <P></P> <P></P> <H3><A name="4__information_disclosure_path_traversal"> </A> Information disclosure via path traversal </H3> <P> <IMG src="/static/cheese_bw.png" style="vertical-align:middle; padding-right: 5px;"> <B>Find a way to read <CODE>secret.txt</CODE> from a running Gruyere server.</B> </P> <P> Amazingly, this attack is not even necessary in many cases: people often install applications and never change the defaults. So the first thing an attacker would try is the default value. </P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'read_secret_txt_hint');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="read_secret_txt_hint" style="display:none"> <P> This isn't a black box attack because you need to know that the <CODE>secret.txt</CODE> file exists, where it's stored, and where Gruyere stores its resource files. You don't need to look at any source code. </P> <P></P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'read_secret_txt_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="read_secret_txt_hint2" style="display:none"> <P> How does the server know which URLs represent resource files? You can use curl or a web proxy to craft request URLs that some browsers may not allow. </P> <P></P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'read_secret_txt_sol');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="read_secret_txt_sol" style="display:none"> <P> <B>To exploit,</B> you can steal <CODE>secret.txt</CODE> via this URL: </P> <P></P> <PRE> https://google-gruyere.appspot.com/382665580745386307547168512335551204731/../secret.txt </PRE> <P> Some browsers, like Firefox and Chrome, optimize out <CODE>../</CODE> in URLs. This doesn't provide any security protection because an attacker will use <CODE>%2f</CODE> to represent <CODE>/</CODE> in the URL; or a tool like curl, a web proxy or a browser that doesn't do that optimization. But if you test your application with one of these browsers to see if you're vulnerable, you might think you were protected when you're not. </P> <P> <B>To fix,</B> we need to prevent access to files outside the resources directory. Validating file paths is a bit tricky as there are various ways to hide path elements like "../" or "~" that allow escaping out of the resources folder. The best protection is to only serve specific resource files. You can either hardcode a list or when your application starts, you can crawl the resource directory and build a list of files. Then only accept requests for those files. You can even do some optimization here like caching small files in memory which will make your application faster. If you are going to try to file path validation, you need to do it on the final path, not on the URL, as there are numerous ways to represent the same characters in URLs. <I>Note: Changing file permissions will NOT work. Gruyere has to be able to read this file.</I> </P> <P></P> </DIV> </DIV> <P></P> <H3><A name="4__data_tampering_path_traversal"> </A> Data tampering via path traversal </H3> <P> <IMG src="/static/cheese_bw.png" style="vertical-align:middle; padding-right: 5px;"> <B>Find a way to replace <CODE>secret.txt</CODE> on a running Gruyere server.</B> </P> <P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'write_secret_txt_hint');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="write_secret_txt_hint" style="display:none"> <P> Again, this isn't a black box attack because you need to know about the directory structure that Gruyere uses, specifically where uploaded files are stored. </P> <P></P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'write_secret_txt_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="write_secret_txt_hint2" style="display:none"> <P> If I log in as user <CODE>brie</CODE> and upload a file, where does the server store it? Can you trick the server into uploading a file to <CODE>../../secret.txt</CODE>? </P> <P></P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'write_secret_txt_sol');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="write_secret_txt_sol" style="display:none"> <P> <B>To exploit,</B> create a new user named <CODE>..</CODE> and upload your new <CODE>secret.txt</CODE>. You could also create a user named <CODE>brie/../..</CODE>. </P> <P> <B>To fix,</B> you should escape dangerous characters in the username (replacing them with safe characters) before using it. It was earlier suggested that we should restrict the characters allowed in a username, but it probably didn't occur to you that <CODE>"."</CODE> was a dangerous character. It's worth noting that there's a vulnerability unique to Windows servers with this implementation. On Windows, filenames are not case sensitive but Gruyere usernames are. So one user can attack another user's files by creating a similar username that differs only in case, e.g., <CODE>BRIE</CODE> instead of <CODE>brie</CODE>. So we need to not just escape unsafe characters but convert the username to a canonical form that is different for different usernames. Or we could avoid all these issues by assigning each user a unique identifier instead. </P> <P> <B>Oops!</B> This doesn't completely solve the problem. Even with the above fix in place, there is another way to perform this attack. Can you find it? <H4 style="cursor: pointer" onclick="toggleBlock(this, 'another_write_secret_hint');"><IMG src="/static/closed.gif"> Hint </H4> <DIV id="another_write_secret_hint" style="display:none"> <P> Are there any limits on the filename when you do an upload? You may need to use a special tool like <CODE>curl</CODE> or a web proxy to perform this attack. </P> <P></P> </DIV> </P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'write_secret_txt_sol2');"><IMG src="/static/closed.gif"> Another Exploit and Fix </H4> <DIV id="write_secret_txt_sol2" style="display:none"> <P> Surprisingly, you can upload a file named <CODE>../secret.txt</CODE>. Gruyere provides no protection against this attack. Most browsers won't let you upload that file but, again, you can do it with curl or other tools. You need the same kind of protection when writing files as you do on read. </P> <P> As a general rule, you should never store user data in the same place as your application files but that alone won't protect against these attacks since if the user can inject <CODE>../</CODE> into the file path, they can traverse all the way to the root of the file system and then back down to the normal install location of your application (or even the Python interpreter itself). </P></DIV></DIV> </DIV> <BR><P></P> <H2><A name="4__denial_of_service"> </A> Denial of Service </H2> <P> A denial of service (DoS) attack is an attempt to make a server unable to service ordinary requests. A common form of DoS attack is sending more requests to a server than it can handle. The server spends all its time servicing the attacker's requests that it has very little time to service legitimate requests. Protecting an application against these kinds of DoS attacks is outside the scope of this codelab. And attacking Gruyere in this way would be interpreted as an attack on App Engine. </P><P> Hackers can also prevent a server from servicing requests by taking advantage of server bugs, such as sending requests that crash a server, make it run out of memory, or otherwise cause it fail serving legitimate requests in some way. In the next few challenges, you'll take advantage of bugs in Gruyere to perform DoS attacks. </P> <H3><A name="4__dos_quit_server"> </A> DoS - Quit the Server </H3> The simplest form of denial of service is shutting down a service. <P> <IMG src="/static/cheese_bw.png" style="vertical-align:middle; padding-right: 5px;"> <B>Find a way to make the server quit.</B> </P> <P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'dos_quit_hint');"><IMG src="/static/closed.gif"> Hint </H4> <DIV id="dos_quit_hint" style="display:none"> <P> How does an administrator make the server quit? The server management page is <CODE><A href="/code/?resources/manage.gtl">manage.gtl</A></CODE>. </P> <P></P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'dos_quit_soln');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="dos_quit_soln" style="display:none"> <P> <B>To exploit,</B> make a request to <CODE>https://google-gruyere.appspot.com/382665580745386307547168512335551204731/quitserver</CODE>. You should need to be logged in as an administrator to do this, but you don't. </P><P> This is another example of a common bug. The server protects against non-administrators accessing certain URLs but the list includes <CODE>/quit</CODE> instead of the actual URL <CODE>/quitserver</CODE>. </P> <P> <B>To fix,</B> add <CODE>/quitserver</CODE> to the URLS only accessible to administrators: </P><PRE> _PROTECTED_URLS = [ "/quitserver", "/reset" ] </PRE> <P> <!--MARK-A--> <B>Oops!</B> This doesn't completely solve the problem. The <CODE>reset</CODE> URL is in the protected list. Can you figure out how to access it? </P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'dos_bonus_hint');"><IMG src="/static/closed.gif"> Hint </H4> <DIV id="dos_bonus_hint" style="display:none"> <P> Look carefully at the code that handles URLs and checks for protected ones. </P></DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'dos_bonus_soln');"><IMG src="/static/closed.gif"> Another Exploit and Fix </H4> <DIV id="dos_bonus_soln" style="display:none"> <P> <B>To exploit,</B> use <CODE>https://google-gruyere.appspot.com/382665580745386307547168512335551204731/RESET</CODE>. The check for protected urls is case sensitive. After doing that check, it capitalizes the string to look up the implementation. This is a classic check/use bug where the condition being checked does not match the actual use. This vulnerability is worse than the previous one because it exposes all the protected urls. </P><P> <B>To fix,</B> put the security check inside the dangerous functions rather than outside them. That ensures that no matter how we get there, the security check can't be skipped. </P></DIV> </DIV> </DIV> <P> <H3><A name="4__dos_overload_server"></A> DoS - Overloading the Server </H3> <P> <IMG src="/static/cheese_b.png" style="vertical-align:middle; padding-right: 5px;"> <B>Find a way to overload the server when it processes a request.</B> </P> <P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'dos_overload_hint1');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="dos_overload_hint1" style="display:none"> <P> You can upload a template that does this. </P></DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'dos_overload_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="dos_overload_hint2" style="display:none"> <P> Every page includes the <CODE><A href="/code/?resources/menubar.gtl">menubar.gtl</A></CODE> template. Can you figure out how to make that template overload the server? </P></DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'dos_overload_soln');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="dos_overload_soln" style="display:none"> <P> <B>To exploit,</B> create a file named <CODE>menubar.gtl</CODE> containing: <PRE> [[include:menubar.gtl]]DoS[[/include:menubar.gtl]] </PRE> and upload it to the <CODE>resources</CODE> directory using a path traversal attack, e.g., creating a user named <CODE>../resources</CODE>. </P> <P> <B>To fix,</B> implement the protections against path traversal and uploading templates discussed earlier. </P> </DIV> <B>NOTE:</B> After performing the previous exploit, you'll need to push the <A href="/part1#1__reset_button">reset button</A>. </P> </DIV> <H3><A name="4__more_dos"> </A> More on Denial of Service </H3> <P> Unlike a well defined vulnerability like XSS or XSRF, denial of service describes a wide class of attacks. This might mean bringing your service down or flooding your inbox so you can't receive legitimate mail. Some things to consider: </P> <P></P> <UL> <LI><P> If you were evil and greedy, how quickly could you take down your application or starve all of its resources? For example, is it possible for a user to upload their hard drive to your application? Entering the attacker's mindset can help identify DoS points in your application. Additionally, think about where the computationally and memory intensive tasks are in your application and put safeguards in place. Do sanity checks on input values. </P> <LI><P>Put monitoring in place so you can detect when you are under attack and enforce per user quotas and rate limiting to ensure that a small subset of users cannot starve the rest. Abusive patterns could include increased memory usage, higher latency, or more requests or connections than usual. </P> </UL> <!--MARK-B--> <BR><P></P> <H2><A name="4__code_execution"> </A> Code Execution </H2> <P> If an attacker can execute arbitrary code remotely on your server, it's usually game over. They may be able to take control over the running program or potentially break out the process to open a new shell on the computer. From here, it's usually not hard to compromise the entire machine the server is running on. </P> <P> Similar to information disclosure and denial of service, there is no recipe or specific defense to prevent remote code execution. The program must perform validation of all user input before handling it and where possible, implement functions with least privilege rights. This topic can't be done justice in just a short paragraph, but know that this is likely the scariest results a security bug can have and trumps any of the above attacks. </P> <P></P> <H3><A name="4__code_execution_challenge"> </A> Code Execution Challenge </H3> <P> <IMG src="/static/cheese_w.png" style="vertical-align:middle; padding-right: 5px;"> <B>Find a code execution exploit.</B> </P> <P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'code_upload_hint');"><IMG src="/static/closed.gif"> Hint </H4> <DIV id="code_upload_hint" style="display:none"> <P> You need to use two previous exploits. </P></DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'code_upload_soln');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="code_upload_soln" style="display:none"> <P> <B>To exploit,</B> make a copy of <CODE><A href="/code/?gtl.py">gtl.py</A></CODE> (or <CODE><A href="/code/?sanitize.py">sanitize.py</A></CODE>) and add some exploit code. Now you can either upload a file named <CODE>../gtl.py</CODE> or create a user named <CODE>..</CODE> and upload <CODE>gtl.py</CODE>. Then, make the server quit by browsing to <CODE>https://google-gruyere.appspot.com/382665580745386307547168512335551204731/quitserver</CODE>. When the server restarts, your code will run. </P> <P> This attack was possible because Gruyere has permission to both read and write files in the Gruyere directory. Applications should run with the minimal privileges possible. </P> <P> Why would you attack <CODE>gtl.py</CODE> or <CODE>sanitize.py</CODE> rather than <CODE>gruyere.py</CODE>? When an attacker has a choice, they would usually choose to attack the infrastructure rather than the application itself. The infrastructure is less likely to be updated and less likely to be noticed. When was the last time you checked that no one had replaced <CODE>python.exe</CODE> with a trojan? </P> <P> <B>To fix,</B> fix the two previous exploits. </P> <P></P> </DIV> </DIV> <P></P> <H3><A name="4__more_code_execution"> </A> More on Remote Code Execution </H3> <P> Even though there is no single or simple defense to remote code execution, here is a short list of some preventative measures: </P> <UL> <LI><P><B>Least Privilege:</B> Always run your application with the <A href="https://www.google.com/search?q=least+privileges" target="_top">least privileges</A> it needs. </P> <LI><P><B>Application Level Checks:</B> Avoid passing user input directly into commands that evaluate arbitrary code, like <CODE>eval()</CODE> or <CODE>system()</CODE>. Instead, use the user input as a switch to choose from a set of developer controlled commands. </P> <LI><P><B>Bounds Checks:</B> Implement proper bounds checks for non-safe languages like C++. Avoid <A href="https://www.google.com/search?q=unsafe+string+functions" target="_top">unsafe string functions</A>. Keep in mind that even safe languages like Python and Java use native libraries. </P> </UL> <!--MARK-C--> <BR><P></P> <FONT SIZE="+2"> <A href="/part5">Continue >></A> </FONT><BR> <BR> <P style="font-size:x-small"> © Google 2017 <A href="https://www.google.com/intl/en/policies/terms/">Terms of Service</A> <BR>The code portions of this codelab are licensed under the Creative Commons Attribution-No Derivative Works 3.0 United States license <<A href="https://creativecommons.org/licenses/by-nd/3.0/us/">https://creativecommons.org/licenses/by-nd/3.0/us</A>>. Brief excerpts of the code may be used for educational or instructional purposes provided this notice is kept intact. Except as otherwise noted the remainder of this codelab is licensed under the Creative Commons Attribution 3.0 United States license <<A href="https://creativecommons.org/licenses/by/3.0/us/">https://creativecommons.org/licenses/by/3.0/us</A>>. </P> </BODY></HTML> |
| URL | https://google-gruyere.appspot.com/part5 |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 365 bytes. |
GET https://google-gruyere.appspot.com/part5 HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: https://google-gruyere.appspot.com/ Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 301 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: caef2034c3d8cd73b1c67904e7d53a00 Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 25303 Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 |
| Response Body - size: 25,303 bytes. |
<HTML><HEAD><META http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<TITLE>Web Application Exploits and Defenses</TITLE> <LINK type="text/css" rel="stylesheet" href="../static/codelab.css"> <!--MARK-Z--> <SCRIPT> function toggleBlock(heading, whichID) { var image = heading.childNodes[0]; var block = document.getElementById(whichID); if (block) { if (getDisplay(block) == 'block') { block.style.display = 'none'; image.src = 'static/closed.gif'; } else { // "none" or "" block.style.display = 'block'; image.src = 'static/open.gif'; } } } function getDisplay(block) { var value = block.style.display; if (!value) { if (document.defaultView) { var computedStyle = document.defaultView.getComputedStyle(block, ""); value = computedStyle.getPropertyValue('display'); } else if (block.currentStyle) { value = block.currentStyle.display; } } return value; } </SCRIPT> </HEAD> <BODY bgcolor="#ffffff"> <DIV class="banner"></DIV> <H1><FONT size="+2"><IMG src="/static/gruyere-78.png" style="vertical-align:middle"> Web Application Exploits and Defenses (Part 5)</FONT> </H1> <P> A Codelab by Bruce Leban, Mugdha Bendre, and Parisa Tabriz </P> <DIV class="printable"> <DIV class="column1"> Table of Contents <UL> <LI class="L1" style="padding-top:0pt"> <A href="/#0__hackers">Beat the hackers</A></LI> <LI class="L1" style="padding-top:0pt"> <A href="/#0__gruyere">Gruyere</A></LI> <LI class="L1"> <A href="/part1#1__setup">Set-up</A> <UL> <LI class="L2"> <A href="/part1#1__reset_button">Reset Button</A></LI> <LI class="L2"> <A href="/part1#1__about_the_code">About the Code</A></LI> <LI class="L2"> <A href="/part1#1__features_and_technologies">Features and Technologies</A></LI> </UL></LI> <LI class="L1"> <A href="/part1#1__using_gruyere">Using Gruyere</A></LI> <LI class="L1"> <A href="/part2#2__cross_site_scripting">Cross-Site Scripting (XSS)</A> <UL> <LI class="L2"> <A href="/part2#2__xss_challenge">XSS Challenges</A></LI> <LI class="L2"> <A href="/part2#2__file_upload_xss">File Upload XSS</A></LI> <LI class="L2"> <A href="/part2#2__reflected_xss">Reflected XSS</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss">Stored XSS</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss_via_html_attribute">Stored XSS via HTML Attribute</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss_via_ajax">Stored XSS via AJAX</A></LI> <LI class="L2"> <A href="/part2#2__reflected_xss_via_ajax">Reflected XSS via AJAX</A></LI> <LI class="L2"> <A href="/part2#2__more_about_xss">More about XSS</A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__client_state_manipulation">Client-State Manipulation</A> <UL> <LI class="L2"> <A href="/part3#3__elevation_of_privilege"> Elevation of Privilege </A></LI> <LI class="L2"> <A href="/part3#3__cookie_manipulation"> Cookie Manipulation </A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__cross_site_request_forgery">Cross-Site Request Forgery (XSRF)</A> <UL> <LI class="L2"> <A href="/part3#3__xsrf_challenge">XSRF Challenge</A></LI> <LI class="L2"> <A href="/part3#3__more_about_preventing_xsrf">More about preventing XSRF</A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__cross_site_script_inclusion">Cross Site Script Inclusion (XSSI)</A> <UL> <LI class="L2"> <A href="/part3#3__xssi_challenge">XSSI Challenge</A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__path_traversal">Path Traversal</A> <UL> <LI class="L2"> <A href="/part4#4__information_disclosure_path_traversal">Information disclosure via path traversal </A></LI> <LI class="L2"> <A href="/part4#4__data_tampering_path_traversal">Data tampering via path traversal </A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__denial_of_service">Denial of Service</A> <UL> <LI class="L2"> <A href="/part4#4__dos_quit_server">DoS - Quit the Server</A></LI> <LI class="L2"> <A href="/part4#4__dos_overload_server">DoS - Overloading the Server</A></LI> <LI class="L2"> <A href="/part4#4__more_dos">More on Denial of Service</A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__code_execution">Code Execution</A> <UL> <LI class="L2"> <A href="/part4#4__code_execution_challenge">Code Execution Challenge</A></LI> <LI class="L2"> <A href="/part4#4__more_code_execution">More on Remote Code Execution</A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__configuration_vulnerabilities">Configuration Vulnerabilities</A> <UL> <LI class="L2"> <A href="/part5#5__information_disclosure_config_1">Information disclosure #1</A></LI> <LI class="L2"> <A href="/part5#5__information_disclosure_config_2">Information disclosure #2</A></LI> <LI class="L2"> <A href="/part5#5__information_disclosure_bug_3">Information disclosure #3 </A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__ajax_vulnerabilities">AJAX vulnerabilities</A> <UL> <LI class="L2"> <A href="/part5#5__dos_via_ajax">DoS via AJAX</A></LI> <LI class="L2"> <A href="/part5#5__phishing_via_ajax">Phishing via AJAX</A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__other_vulnerabilities"> Other Vulnerabilities </A> </H2> <UL> <LI class="L2"> <A href="/part5#5__buffer_and_integer_overflow">Buffer Overflow and Integer Overflow</A></LI> <LI class="L2"> <A href="/part5#5__sql_injection"> SQL Injection </A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__after_the_codelab">After the Codelab</A></LI> </UL> </DIV> <DIV class="column2"> <P></P> <P></P> <P> <NOAUTOLINK> </NOAUTOLINK></P> <H2><A name="5__configuration_vulnerabilities"> </A> Configuration Vulnerabilities </H2> <P> Applications are often installed with default settings that attackers can use to attack them. This is particularly an issue with third party software where an attacker has easy access to a copy of the same application or framework you are running. Hackers know the default account names and passwords. For example, looking at the contents of <CODE><A href="/code/?data.py">data.py</A></CODE> you know that there's a default administrator account named 'admin' with the password 'secret'. </P> <P> Configuration vulnerabilities also include features that increase attack surface. A common example is a feature that is on by default but you are not using, so you didn't configure it and the default configuration is vulnerable. It also includes debug features like status pages or dumping stack traces on failures. </P> <P></P> <H3><A name="5__information_disclosure_config_1"> </A> Information disclosure #1 </H3> <P> <IMG src="/static/cheese_w.png" style="vertical-align:middle; padding-right: 5px;"> <B>Read the contents of the database off of a running server by exploiting a configuration vulnerability.</B> </P> <P>You should look through the Gruyere code looking for default configurations or debug features that don't belong there.</P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'disclosure_1_hint');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="disclosure_1_hint" style="display:none"> <P> Look at all the files installed with Gruyere. Are there any files that shouldn't be there? </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'disclosure_1_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="disclosure_1_hint2" style="display:none"> <P> Look for a <CODE>.gtl</CODE> file that isn't referenced anywhere. </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'disclosure_1_sol');"><IMG src="/static/closed.gif"> Exploit and Fixes </H4> <DIV id="disclosure_1_sol" style="display:none"> <P> <B>To exploit,</B> you can use the debug dump page <CODE><A href="/code/?resoources/dump.gtl">dump.gtl</A></CODE> to display the contents of the database via the following URL: </P><PRE> https://google-gruyere.appspot.com/382665580745386307547168512335551204731/dump.gtl </PRE> <P> <B>To fix,</B> always make sure debug features are not installed. In this case, delete <CODE>dump.gtl</CODE>. This is an example of the kind of debug feature that might be left in an application by mistake. If a debug feature like this is necessary, then it needs to be carefully locked down: only admin users should have access and only requests from debug IP addresses should be accepted. </P><P> This exploit exposes the users' passwords. Passwords should never be stored in cleartext. Instead, you should use <A href="https://www.google.com/search?q=password+hashing">password hashing</A>. The idea is that to authenticate a user, you don't need to know their password, only be convinced that the user knows it. When the user sets their password, you store only a cryptographic hash of the password and a salt value. When the user re-enters their password later, you recompute the hash and if it matches you conclude the password is correct. If an attacker obtains the hash value, it's very difficult for them to reverse that to find the original password. (Which is a good thing, since despite <A href="https://www.google.com/search?q=choosing+a+good+password">lots of advice</A> to the contrary, users frequently use the same weak passwords for multiple sites.) </P></DIV> </DIV> <P></P> <H3><A name="5__information_disclosure_config_2"> </A> Information disclosure #2 </H3> <P> <IMG src="/static/cheese_w.png" style="vertical-align:middle; padding-right: 5px;"> <B>Even after implementing the fix described above, an attacker can undo it and execute the attack! How can that be?</B> </P> <P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'disclosure_2_hint');"><IMG src="/static/closed.gif"> Hint </H4> <DIV id="disclosure_2_hint" style="display:none"> <P> You can upload a file of any type. </P> <P></P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'disclosure_2_sol');"><IMG src="/static/closed.gif"> Exploit and Fixes </H4> <DIV id="disclosure_2_sol" style="display:none"> <P> <B>To exploit,</B> Gruyere allows the user to upload files of any type, including <CODE>.gtl</CODE> files. So the attacker can simply upload their own copy of <CODE><A href="/code/?resources/dump.gtl">dump.gtl</A></CODE> or a similar file and than access it. In fact, as noted earlier, hosting arbitrary content on the server is a major security risk whether it's HTML, JavaScript, Flash or something else. Allowing a file with an unknown file type may lead to a security hole in the future. </P> <P> <B>To fix,</B> we should do several things: </P><OL> <LI> Only files that are part of Gruyere should be treated as templates. </LI> <LI> Don't store user uploaded files in the same place as application files. </LI> <LI> Consider limiting the types of files that can be uploaded (via a whitelist). </LI> </OL> </DIV> </DIV> <H3><A name="5__information_disclosure_bug_3"> </A> Information disclosure #3 </H3> <P> <IMG src="/static/cheese_w.png" style="vertical-align:middle; padding-right: 5px;"> <B>Even after implementing the fixes described above, a similar attack is still possible through a different attack vector. Can you find it?</B> </P> <P> This attack isn't a a configuration vulnerability, just bad code. </P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'disclosure_3_hint1');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="disclosure_3_hint1" style="display:none"> <P>You can insert something in your private snippet which will display the contents of the database.</P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'disclosure_3_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="disclosure_3_hint2" style="display:none"> <P>This attack is closely related to the previous ones. There is a bug in the code that expands templates that you can exploit.</P> </DIV> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'disclosure_3_sol');"><IMG src="/static/closed.gif"> Exploit and Fixes </H4> <DIV id="disclosure_3_sol" style="display:none"> <P> There is a defect in Gruyere's template expansion code that reparses expanded variables. Specifically, when expanding a block it expands variables in the block. Then it parses the block as a template and expands variables again, </P> <B>To exploit,</B> add this to your private snippet: <PRE> {{_db:pprint}} </PRE> </P> <P> <B>To fix,</B> modify the template code so it never reparses inserted variable values. The defect in the code is due to the fact that <CODE>ExpandTemplate</CODE> calls <CODE>_ExpandBlocks</CODE> followed by <CODE>_ExpandVariables</CODE>, but <CODE>_ExpandBlocks</CODE> calls <CODE>ExpandTemplate</CODE> on nested blocks. So if a variable is expanded inside a nested block and contains something that looks like a variable template, it will get expanded a second time. That sounds complicated because it is complicated. Parsing blocks and variables separately is a fundamental flaw in the design of the expander, so the fix is non-trivial. </P> <P> This exploit is possible because the template language allows arbitrary database access. It would be safer if the templates were only allowed to access data specifically provided to them. For example, a template could have an associated database query and only the data matched by that query would be passed to the template. This would limit the scope of a bug like this to data that the user was already allowed to access. </P> </DIV> </DIV> <BR><BR> </P><H2><A name="5__ajax_vulnerabilities"> </A> AJAX vulnerabilities </H2> Bad AJAX code allows attackers to modify parts of your application in ways that you might not expect. In traditional client development, there is a clear separation between the application and the data it displays. That's not true in web applications as the next two attacks will make clear. <P></P> <H3><A name="5__dos_via_ajax"> </A> DoS via AJAX </H3> <P> <IMG src="/static/cheese_b.png" style="vertical-align:middle; padding-right: 5px;"> <B>Find an attack that prevents users from seeing their private snippets on the home page.</B> (The attack should be triggered after clicking the refresh link and without using XSS.) </P> <P></P> <DIV style="margin-left:18pt"> <P></P> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'dom_hint');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="dom_hint" style="display:none"> <P> Can you figure out how to change the value of the private snippet in the AJAX response? </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'dom_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="dom_hint2" style="display:none"> <P> What happens if a JSON object has a duplicate key value? </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'dom_sol');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="dom_sol" style="display:none"> <P> <B>To exploit,</B> create a user named <CODE>private_snippet</CODE> and create at least one snippet. The JSON response will then be <CODE>{'private_snippet' : <user's private snippet>, ..., 'private_snippet' : <attacker's snippet>}</CODE> and the attacker's snippet replaces the user's. </P> <P> <B>To fix,</B> the AJAX code needs to make sure that the data only goes where it's supposed to go. The flaw here is that the JSON structure is not robust. A better structure would be <CODE>[<private_snippet>, {<user> : <snippet>,...}]</CODE>. </P> <P></P> </DIV> </DIV> <P></P> <H3><A name="5__phishing_via_ajax"> </A> Phishing via AJAX </H3> <P> While the previous attack may seem like a minor inconvenience, careless DOM manipulation can lead to much more serious problems. </P> <P> <IMG src="/static/cheese_b.png" style="vertical-align:middle; padding-right: 5px;"> <B>Find a way to change the sign in link in the upper right corner to point to <CODE>https://evil.example.com</CODE>.</B> </P> <P> (The attack should be triggered after clicking the refresh link and without using XSS or a script.) </P> <P></P> <DIV style="margin-left:18pt"> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'phishing_hint');"><IMG src="/static/closed.gif"> Hint 1 </H4> <DIV id="phishing_hint" style="display:none"> <P> Look at what the script does to replace the snippets on the page. Can you get it to replace the sign in link? </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'phishing_hint2');"><IMG src="/static/closed.gif"> Hint 2 </H4> <DIV id="phishing_hint2" style="display:none"> <P> Look at the AJAX code to see how it replaces each snippet, and then look at the structure of the home page and see if you can see what else you might be able to replace. (You can't just replace the sign in link. You'll have to replace a bit more.) </P> <P></P> </DIV> <H4 style="cursor: pointer" onclick="toggleBlock(this, 'phishing_sol');"><IMG src="/static/closed.gif"> Exploit and Fix </H4> <DIV id="phishing_sol" style="display:none"> <P> <B>To exploit,</B> create a user named <CODE>menu-right</CODE> and publish a snippet that looks exactly like the right side of the menu bar. </P> <P></P> <PRE> <a href='https://evil.example.com/login'>Sign in</a> | <a href='https://evil.example.com/newaccount.gtl'>Sign up</a> </PRE> <P> If the user is already logged in, the menu bar will look wrong. But that's ok, since there's a good chance the user will just think they somehow accidentally got logged out of the web site and log in again. </P> <P> <B>To fix,</B> the process of modifying the DOM needs to be made more robust. When user values are used in as DOM element identifiers, you should ensure that there can't be a conflict as there is here, for example, applying a prefix to user values like <CODE>id="user_<USER>"</USER></CODE>. Even better, use your own identifiers rather than user values. </P> <P> This spoofing attack is easily detected when the user clicks Sign in and ends up at <CODE>evil.example.com</CODE>. A clever attacker could do something harder to detect, like replacing the Sign in link with a script that renders the sign in form on the current page with the form submission going to their server. </P> <P></P> </DIV> </DIV> <BR><P></P> <P></P> <H2><A name="5__other_vulnerabilities"> </A> Other Vulnerabilities </H2> <H3><A name="5__buffer_and_integer_overflow"> </A> Buffer Overflow and Integer Overflow </H3> <DIV><P>A <A href="https://www.google.com/search?q=buffer+overflow" target="_top">buffer overflow</A> vulnerability exists when an application does not properly guard its buffers and allow user data to write past the end of a buffer. This excess data can modify other variables, including pointers and function return addresses, leading to arbitrary code execution. Historically, buffer overflow vulnerabilities have been responsible for some of the most widespread internet attacks including <a href="https://www.google.com/search?q=sql+slammer">SQL Slammer<a>, <a href="https://www.google.com/search?q=blaster+worm">Blaster</a> and <a href="https://www.google.com/search?q=code+red+worm">Code Red</a> computer worms. The PS2, Xbox and Wii have all been hacked using buffer overflow exploits. </P> <P>While not as well known, <A href="https://www.google.com/search?q=integer+overflow+vulnerability" target="_top">integer overflow</A> vulnerabilities can be just as dangerous. Any time an integer computation silently returns an incorrect result, the application will operate incorrectly. In the best case, the application fails. In the worst case, there is a security bug. For example, if an application checks that <CODE>length + 1 < limit</CODE> then this will succeed if <CODE>length</CODE> is the largest positive integer value, which can then expose a buffer overflow vulnerability. </P> <P>This codelab doesn't cover overflow vulnerabilities because Gruyere is written in Python, and therefore not vulnerable to typical buffer and integer overflow problems. Python won't allow you to read or write outside the bounds of an array and integers can't overflow. While C and C++ programs are most commonly known to expose these vulnerabilities, other languages are not immune. For example, while Java was designed to prevent buffer overflows, it silently ignores integer overflow. </P> <P> Like all applications, Gruyere is vulnerable to platform vulnerabilities. That is, if there are security bugs in the platforms that Gruyere is built on top of, then those bugs would also apply to Gruyere. Gruyere's platform includes: the Python runtime system and libraries, AppEngine, the operating system that Gruyere runs on and the client side software (including the web browser) that users use to run Gruyere. While platform vulnerabilities are important, they are outside the scope of this codelab as you generally can't fix platform vulnerabilities by making changes to your application. Fixing platform vulnerabilities yourself is also not practical for many people, but you can mitigate your risks by making sure that you are diligent in applying security updates as they are released by platform vendors. </P> </DIV> <!--MARK-D--> <H3><A name="5__sql_injection"> </A> SQL Injection </H3> <DIV><P>Just as XSS vulnerabilities allow attackers to inject script into web pages, <a href="https://www.google.com/search?q=sql+injection">SQL injection</a> vulnerabilities allow attackers to inject arbitrary scripts into SQL queries. When a SQL query is executed it can either read or write data, so an attacker can use SQL injection to read your entire database as well as overwrite it, as described in the classic <a href="https://xkcd.com/327/">Bobby Tables</a> XKCD comic. If you use SQL, the most important advice is to avoid building queries by string concatenation: use API calls instead. This codelab doesn't cover SQL injection because Gruyere doesn't use SQL. </P> </DIV> <BR><BR> <H2><A name="5__after_the_codelab"> </A> After the Codelab </H2> <P> We hope that you found this codelab instructive. If you want more practice, there are many more security bugs in Gruyere than the ones described above. You should attack your own application using what you've learned and write unit tests to verify that these bugs are not present and won't get introduced in the future. You should also consider using <A href="https://www.google.com/search?q=fuzz+testing" target="_top">fuzz testing</A> tools. For more information about security at Google, please visit our <A href="https://security.googleblog.com/" target="_top">blog</A> or our <A href="https://www.google.com/about/appsecurity/" target="_top">corporate security page</A>. </P> <P> If you'd like to share this codelab with others, please consider tweeting or buzzing about it or posting one of these badges on your blog or personal page: <!--MARK-E--> </P> <div> <table style="border-collapse:collapse;border:solid 1 black" cellpadding="5"> <tr><td style="border:solid 1 black;text-align:center;vertical-align:middle"> <a href="https://google-gruyere.appspot.com/"> <img src="../static/gruyere-badge.png" style="padding:4pt" border="0" alt="Learn how to make web apps more secure. Do the Gruyere codelab."></a> </td><td style="border:solid 1 black;vertical-align:middle"> <pre style="margin:0"> <a href="https://google-gruyere.appspot.com/"> <img src="//google-gruyere.appspot.com/static/gruyere-badge.png" style="padding:4pt" border="0" alt="Learn how to make web apps more secure. Do the Gruyere codelab."></a> </pre> <div align="center" style="margin:0">(Line breaks above should be copied verbatim.)</div> </td></tr><tr><td style="border:solid 1 black;text-align:center;vertical-align:middle"> <a href="https://google-gruyere.appspot.com/"> <img src="../static/gruyere-40.png" style="padding:4pt" border="0" title="Learn how to make web apps more secure. Do the Gruyere codelab." alt="Learn how to make web apps more secure. Do the Gruyere codelab."></a> </td><td style="border:solid 1 black;vertical-align:middle"> <pre style="margin:0"> <a href="https://google-gruyere.appspot.com/"> <img src="//google-gruyere.appspot.com/static/gruyere-40.png" style="padding:4pt" border="0" title="Learn how to make web apps more secure. Do the Gruyere codelab." alt="Learn how to make web apps more secure. Do the Gruyere codelab."></a> </pre> <div align="center" style="margin:0">(Line breaks above should be copied verbatim.)</div> </td></tr></table></div> <!--MARK-F--> <!--NEXTLINK--> <BR> <P style="font-size:x-small"> © Google 2017 <A href="https://www.google.com/intl/en/policies/terms/">Terms of Service</A> <BR>The code portions of this codelab are licensed under the Creative Commons Attribution-No Derivative Works 3.0 United States license <<A href="https://creativecommons.org/licenses/by-nd/3.0/us/">https://creativecommons.org/licenses/by-nd/3.0/us</A>>. Brief excerpts of the code may be used for educational or instructional purposes provided this notice is kept intact. Except as otherwise noted the remainder of this codelab is licensed under the Creative Commons Attribution 3.0 United States license <<A href="https://creativecommons.org/licenses/by/3.0/us/">https://creativecommons.org/licenses/by/3.0/us</A>>. </P> </BODY></HTML> |
| URL | https://google-gruyere.appspot.com/start |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 369 bytes. |
GET https://google-gruyere.appspot.com/start HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: https://google-gruyere.appspot.com/123/ Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 281 bytes. |
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8 Cache-Control: no-cache X-Cloud-Trace-Context: f47ca7b10fb640127b87f1e5dad0bcfc Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 756 Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 |
| Response Body - size: 756 bytes. |
<HTML>
<STYLE> body, th, td, form { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; } h1 { color: #dd0000; } </STYLE> <TITLE>Start Gruyere</TITLE> <BODY> <H1>Start Gruyere</H1> Your Gruyere instance id is 382665580745386307547168512335551204731. <br><br><span style="color:red"><b>WARNING: Gruyere is not secure.<br> Do not upload any personal or private data.</b></span> <br><br>By using Gruyere you agree to the <A href="https://www.google.com/intl/en/policies/terms/">terms of service</A>. <H2><A HREF="/382665580745386307547168512335551204731">Resume</A></H2> <H2><A HREF="/resetbutton/382665580745386307547168512335551204731">Reset</A></H2> </BODY></HTML> |
| URL | https://google-gruyere.appspot.com/static/codeindex.html |
| Method | GET |
| Parameter | x-frame-options |
| Attack | |
| Evidence | |
| Request Header - size: 386 bytes. |
GET https://google-gruyere.appspot.com/static/codeindex.html HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: https://google-gruyere.appspot.com/code/ Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 334 bytes. |
HTTP/1.1 200 OK
Date: Tue, 06 Feb 2024 17:42:24 GMT Expires: Tue, 06 Feb 2024 17:52:24 GMT Cache-Control: public, max-age=600 ETag: "3m8CBg" X-Cloud-Trace-Context: ff18a02873c19d522ffa7bb39a8c011b Content-Type: text/html Server: Google Frontend Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 content-length: 2188 |
| Response Body - size: 2,188 bytes. |
<HTML>
<HEAD> <TITLE>Gruyere Code</TITLE> <STYLE> a {color:black; text-decoration:none} a:visited{color:#6600cc;} a:focus, a:hover {color:#003399; text-decoration: underline;} a:active{color:#cc33cc;} big {color: #660066; } </STYLE> </HEAD> <BODY> <TT> <B><BIG><BIG>Gruyere<BR>Code</BIG></BIG></B><BR><BR> <A href="/code/gruyere.py" target="codepage">gruyere.py</A><BR> <A href="/code/data.py" target="codepage">data.py</A><BR> <A href="/code/sanitize.py" target="codepage">sanitize.py</A><BR> <A href="/code/gtl.py" target="codepage">gtl.py</A><BR> <A href="/code/secret.txt" target="codepage">secret.txt</A><BR> <BR> resources/<BR> <A href="/code/resources/base.css" target="codepage">base.css</A><BR> <A href="/code/resources/dump.gtl" target="codepage">dump.gtl</A><BR> <A href="/code/resources/editprofile.gtl" target="codepage">editprofile.gtl</A><BR> <A href="/code/resources/error.gtl" target="codepage">error.gtl</A><BR> <A href="/code/resources/feed.gtl" target="codepage">feed.gtl</A><BR> <A href="/code/resources/home.gtl" target="codepage">home.gtl</A><BR> <A href="/code/resources/lib.js" target="codepage">lib.js</A><BR> <A href="/code/resources/login.gtl" target="codepage">login.gtl</A><BR> <A href="/code/resources/manage.gtl" target="codepage">manage.gtl</A><BR> <A href="/code/resources/menubar.gtl" target="codepage">menubar.gtl</A><BR> <A href="/code/resources/newaccount.gtl" target="codepage">newaccount.gtl</A><BR> <A href="/code/resources/newsnippet.gtl" target="codepage">newsnippet.gtl</A><BR> <A href="/code/resources/showprofile.gtl" target="codepage">showprofile.gtl</A><BR> <A href="/code/resources/snippets.gtl" target="codepage">snippets.gtl</A><BR> <A href="/code/resources/upload.gtl" target="codepage">upload.gtl</A><BR> <A href="/code/resources/upload2.gtl" target="codepage">upload2.gtl</A><BR> <BR> <I><A href="/gruyere-code.zip" target="_top">Download zip file</A></I><BR> <BR> <A href="/" target="_top"><< back to codelab</A> </TT> </BODY> </HTML> |
| Instances | 73 |
| Solution |
Modern Web browsers support the Content-Security-Policy and X-Frame-Options HTTP headers. Ensure one of them is set on all web pages returned by your site/app.
If you expect the page to be framed only by pages on your server (e.g. it's part of a FRAMESET) then you'll want to use SAMEORIGIN, otherwise if you never expect the page to be framed, you should use DENY. Alternatively consider implementing Content Security Policy's "frame-ancestors" directive.
|
| Reference | https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options |
| Tags |
OWASP_2021_A05
WSTG-v42-CLNT-09 OWASP_2017_A06 |
| CWE Id | 1021 |
| WASC Id | 15 |
| Plugin Id | 10020 |
|
Low |
Cookie No HttpOnly Flag |
|---|---|
| Description |
A cookie has been set without the HttpOnly flag, which means that the cookie can be accessed by JavaScript. If a malicious script can be run on this page then the cookie will be accessible and can be transmitted to another site. If this is a session cookie then session hijacking may be possible.
|
| URL | http://google-gruyere.appspot.com/382665580745386307547168512335551204731/saveprofile?action=new&is_author=True&pw=ZAP&uid=ZAP |
| Method | GET |
| Parameter | GRUYERE |
| Attack | |
| Evidence | Set-Cookie: GRUYERE |
| Request Header - size: 504 bytes. |
GET http://google-gruyere.appspot.com/382665580745386307547168512335551204731/saveprofile?action=new&is_author=True&pw=ZAP&uid=ZAP HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/382665580745386307547168512335551204731/newaccount.gtl Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 378 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache Set-Cookie: GRUYERE=26087470|ZAP||author; path=/382665580745386307547168512335551204731 X-XSS-Protection: 0 X-Cloud-Trace-Context: 89a603bf4f00caebb0a57d884f1f61e1 Date: Tue, 06 Feb 2024 17:42:25 GMT Server: Google Frontend Content-Length: 2224 Expires: Tue, 06 Feb 2024 17:42:25 GMT |
| Response Body - size: 2,224 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Error</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/382665580745386307547168512335551204731/'>Home</a> </span> <span id='menu-right'> <a href='/382665580745386307547168512335551204731/login'>Sign in</a> | <a href='/382665580745386307547168512335551204731/newaccount.gtl'>Sign up</a> </span> </div> <div class='message'>Account created.</div> </body> </html> |
| URL | http://google-gruyere.appspot.com/397587545265662818066921574239585949762/saveprofile?action=new&is_author=True&pw=ZAP&uid=ZAP |
| Method | GET |
| Parameter | GRUYERE |
| Attack | |
| Evidence | Set-Cookie: GRUYERE |
| Request Header - size: 504 bytes. |
GET http://google-gruyere.appspot.com/397587545265662818066921574239585949762/saveprofile?action=new&is_author=True&pw=ZAP&uid=ZAP HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/397587545265662818066921574239585949762/newaccount.gtl Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 378 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache Set-Cookie: GRUYERE=26087470|ZAP||author; path=/397587545265662818066921574239585949762 X-XSS-Protection: 0 X-Cloud-Trace-Context: 88931a38df2e52e671b2c4f9f074850c Date: Tue, 06 Feb 2024 17:42:18 GMT Server: Google Frontend Content-Length: 2224 Expires: Tue, 06 Feb 2024 17:42:18 GMT |
| Response Body - size: 2,224 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Error</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/397587545265662818066921574239585949762/'>Home</a> </span> <span id='menu-right'> <a href='/397587545265662818066921574239585949762/login'>Sign in</a> | <a href='/397587545265662818066921574239585949762/newaccount.gtl'>Sign up</a> </span> </div> <div class='message'>Account created.</div> </body> </html> |
| URL | http://google-gruyere.appspot.com/start |
| Method | GET |
| Parameter | GRUYERE_ID |
| Attack | |
| Evidence | Set-Cookie: GRUYERE_ID |
| Request Header - size: 313 bytes. |
GET http://google-gruyere.appspot.com/start HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/robots.txt |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 354 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-Type: text/html; charset=utf-8 Pragma: no-cache Set-Cookie: GRUYERE_ID=382665580745386307547168512335551204731; Path=/ X-Cloud-Trace-Context: cbdf509c9c2a3e252715ab2ea38a1140 Date: Tue, 06 Feb 2024 17:42:17 GMT Server: Google Frontend Content-Length: 681 Expires: Tue, 06 Feb 2024 17:42:17 GMT |
| Response Body - size: 681 bytes. |
<HTML>
<STYLE> body, th, td, form { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; } h1 { color: #dd0000; } </STYLE> <TITLE>Start Gruyere</TITLE> <BODY> <H1>Start Gruyere</H1> Your Gruyere instance id is 382665580745386307547168512335551204731. <br><br><span style="color:red"><b>WARNING: Gruyere is not secure.<br> Do not upload any personal or private data.</b></span> <br><br>By using Gruyere you agree to the <A href="https://www.google.com/intl/en/policies/terms/">terms of service</A>. <H2><A HREF="/382665580745386307547168512335551204731">Agree & Start</A></H2> </BODY></HTML> |
| Instances | 3 |
| Solution |
Ensure that the HttpOnly flag is set for all cookies.
|
| Reference | https://owasp.org/www-community/HttpOnly |
| Tags |
OWASP_2021_A05
WSTG-v42-SESS-02 OWASP_2017_A06 |
| CWE Id | 1004 |
| WASC Id | 13 |
| Plugin Id | 10010 |
|
Low |
Cookie without SameSite Attribute |
|---|---|
| Description |
A cookie has been set without the SameSite attribute, which means that the cookie can be sent as a result of a 'cross-site' request. The SameSite attribute is an effective counter measure to cross-site request forgery, cross-site script inclusion, and timing attacks.
|
| URL | http://google-gruyere.appspot.com/382665580745386307547168512335551204731/saveprofile?action=new&is_author=True&pw=ZAP&uid=ZAP |
| Method | GET |
| Parameter | GRUYERE |
| Attack | |
| Evidence | Set-Cookie: GRUYERE |
| Request Header - size: 504 bytes. |
GET http://google-gruyere.appspot.com/382665580745386307547168512335551204731/saveprofile?action=new&is_author=True&pw=ZAP&uid=ZAP HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/382665580745386307547168512335551204731/newaccount.gtl Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 378 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache Set-Cookie: GRUYERE=26087470|ZAP||author; path=/382665580745386307547168512335551204731 X-XSS-Protection: 0 X-Cloud-Trace-Context: 89a603bf4f00caebb0a57d884f1f61e1 Date: Tue, 06 Feb 2024 17:42:25 GMT Server: Google Frontend Content-Length: 2224 Expires: Tue, 06 Feb 2024 17:42:25 GMT |
| Response Body - size: 2,224 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Error</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/382665580745386307547168512335551204731/'>Home</a> </span> <span id='menu-right'> <a href='/382665580745386307547168512335551204731/login'>Sign in</a> | <a href='/382665580745386307547168512335551204731/newaccount.gtl'>Sign up</a> </span> </div> <div class='message'>Account created.</div> </body> </html> |
| URL | http://google-gruyere.appspot.com/397587545265662818066921574239585949762/saveprofile?action=new&is_author=True&pw=ZAP&uid=ZAP |
| Method | GET |
| Parameter | GRUYERE |
| Attack | |
| Evidence | Set-Cookie: GRUYERE |
| Request Header - size: 504 bytes. |
GET http://google-gruyere.appspot.com/397587545265662818066921574239585949762/saveprofile?action=new&is_author=True&pw=ZAP&uid=ZAP HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/397587545265662818066921574239585949762/newaccount.gtl Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 378 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache Set-Cookie: GRUYERE=26087470|ZAP||author; path=/397587545265662818066921574239585949762 X-XSS-Protection: 0 X-Cloud-Trace-Context: 88931a38df2e52e671b2c4f9f074850c Date: Tue, 06 Feb 2024 17:42:18 GMT Server: Google Frontend Content-Length: 2224 Expires: Tue, 06 Feb 2024 17:42:18 GMT |
| Response Body - size: 2,224 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Error</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/397587545265662818066921574239585949762/'>Home</a> </span> <span id='menu-right'> <a href='/397587545265662818066921574239585949762/login'>Sign in</a> | <a href='/397587545265662818066921574239585949762/newaccount.gtl'>Sign up</a> </span> </div> <div class='message'>Account created.</div> </body> </html> |
| URL | http://google-gruyere.appspot.com/start |
| Method | GET |
| Parameter | GRUYERE_ID |
| Attack | |
| Evidence | Set-Cookie: GRUYERE_ID |
| Request Header - size: 313 bytes. |
GET http://google-gruyere.appspot.com/start HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/robots.txt |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 354 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-Type: text/html; charset=utf-8 Pragma: no-cache Set-Cookie: GRUYERE_ID=382665580745386307547168512335551204731; Path=/ X-Cloud-Trace-Context: cbdf509c9c2a3e252715ab2ea38a1140 Date: Tue, 06 Feb 2024 17:42:17 GMT Server: Google Frontend Content-Length: 681 Expires: Tue, 06 Feb 2024 17:42:17 GMT |
| Response Body - size: 681 bytes. |
<HTML>
<STYLE> body, th, td, form { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; } h1 { color: #dd0000; } </STYLE> <TITLE>Start Gruyere</TITLE> <BODY> <H1>Start Gruyere</H1> Your Gruyere instance id is 382665580745386307547168512335551204731. <br><br><span style="color:red"><b>WARNING: Gruyere is not secure.<br> Do not upload any personal or private data.</b></span> <br><br>By using Gruyere you agree to the <A href="https://www.google.com/intl/en/policies/terms/">terms of service</A>. <H2><A HREF="/382665580745386307547168512335551204731">Agree & Start</A></H2> </BODY></HTML> |
| Instances | 3 |
| Solution |
Ensure that the SameSite attribute is set to either 'lax' or ideally 'strict' for all cookies.
|
| Reference | https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site |
| Tags |
OWASP_2021_A01
WSTG-v42-SESS-02 OWASP_2017_A05 |
| CWE Id | 1275 |
| WASC Id | 13 |
| Plugin Id | 10054 |
|
Low |
Strict-Transport-Security Header Not Set |
|---|---|
| Description |
HTTP Strict Transport Security (HSTS) is a web security policy mechanism whereby a web server declares that complying user agents (such as a web browser) are to interact with it using only secure HTTPS connections (i.e. HTTP layered over TLS/SSL). HSTS is an IETF standards track protocol and is specified in RFC 6797.
|
| URL | https://google-gruyere.appspot.com/ |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 364 bytes. |
GET https://google-gruyere.appspot.com/ HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/part5 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 301 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: 8fa64cb955098684db5c4eca2daf22f7 Date: Tue, 06 Feb 2024 17:42:23 GMT Server: Google Frontend Content-Length: 11506 Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 |
| Response Body - size: 11,506 bytes. |
<HTML><HEAD><META http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<TITLE>Web Application Exploits and Defenses</TITLE> <LINK type="text/css" rel="stylesheet" href="../static/codelab.css"> <!--MARK-Z--> <SCRIPT> function toggleBlock(heading, whichID) { var image = heading.childNodes[0]; var block = document.getElementById(whichID); if (block) { if (getDisplay(block) == 'block') { block.style.display = 'none'; image.src = 'static/closed.gif'; } else { // "none" or "" block.style.display = 'block'; image.src = 'static/open.gif'; } } } function getDisplay(block) { var value = block.style.display; if (!value) { if (document.defaultView) { var computedStyle = document.defaultView.getComputedStyle(block, ""); value = computedStyle.getPropertyValue('display'); } else if (block.currentStyle) { value = block.currentStyle.display; } } return value; } </SCRIPT> </HEAD> <BODY bgcolor="#ffffff"> <DIV class="banner"></DIV> <H1><FONT size="+2"><IMG src="/static/gruyere-78.png" style="vertical-align:middle"> Web Application Exploits and Defenses <!--PART#--></FONT> </H1> <P> A Codelab by Bruce Leban, Mugdha Bendre, and Parisa Tabriz </P> <DIV class="printable"> <DIV class="column1"> Table of Contents <UL> <LI class="L1" style="padding-top:0pt"> <A href="/#0__hackers">Beat the hackers</A></LI> <LI class="L1" style="padding-top:0pt"> <A href="/#0__gruyere">Gruyere</A></LI> <LI class="L1"> <A href="/part1#1__setup">Set-up</A> <UL> <LI class="L2"> <A href="/part1#1__reset_button">Reset Button</A></LI> <LI class="L2"> <A href="/part1#1__about_the_code">About the Code</A></LI> <LI class="L2"> <A href="/part1#1__features_and_technologies">Features and Technologies</A></LI> </UL></LI> <LI class="L1"> <A href="/part1#1__using_gruyere">Using Gruyere</A></LI> <LI class="L1"> <A href="/part2#2__cross_site_scripting">Cross-Site Scripting (XSS)</A> <UL> <LI class="L2"> <A href="/part2#2__xss_challenge">XSS Challenges</A></LI> <LI class="L2"> <A href="/part2#2__file_upload_xss">File Upload XSS</A></LI> <LI class="L2"> <A href="/part2#2__reflected_xss">Reflected XSS</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss">Stored XSS</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss_via_html_attribute">Stored XSS via HTML Attribute</A></LI> <LI class="L2"> <A href="/part2#2__stored_xss_via_ajax">Stored XSS via AJAX</A></LI> <LI class="L2"> <A href="/part2#2__reflected_xss_via_ajax">Reflected XSS via AJAX</A></LI> <LI class="L2"> <A href="/part2#2__more_about_xss">More about XSS</A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__client_state_manipulation">Client-State Manipulation</A> <UL> <LI class="L2"> <A href="/part3#3__elevation_of_privilege"> Elevation of Privilege </A></LI> <LI class="L2"> <A href="/part3#3__cookie_manipulation"> Cookie Manipulation </A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__cross_site_request_forgery">Cross-Site Request Forgery (XSRF)</A> <UL> <LI class="L2"> <A href="/part3#3__xsrf_challenge">XSRF Challenge</A></LI> <LI class="L2"> <A href="/part3#3__more_about_preventing_xsrf">More about preventing XSRF</A></LI> </UL></LI> <LI class="L1"> <A href="/part3#3__cross_site_script_inclusion">Cross Site Script Inclusion (XSSI)</A> <UL> <LI class="L2"> <A href="/part3#3__xssi_challenge">XSSI Challenge</A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__path_traversal">Path Traversal</A> <UL> <LI class="L2"> <A href="/part4#4__information_disclosure_path_traversal">Information disclosure via path traversal </A></LI> <LI class="L2"> <A href="/part4#4__data_tampering_path_traversal">Data tampering via path traversal </A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__denial_of_service">Denial of Service</A> <UL> <LI class="L2"> <A href="/part4#4__dos_quit_server">DoS - Quit the Server</A></LI> <LI class="L2"> <A href="/part4#4__dos_overload_server">DoS - Overloading the Server</A></LI> <LI class="L2"> <A href="/part4#4__more_dos">More on Denial of Service</A></LI> </UL></LI> <LI class="L1"> <A href="/part4#4__code_execution">Code Execution</A> <UL> <LI class="L2"> <A href="/part4#4__code_execution_challenge">Code Execution Challenge</A></LI> <LI class="L2"> <A href="/part4#4__more_code_execution">More on Remote Code Execution</A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__configuration_vulnerabilities">Configuration Vulnerabilities</A> <UL> <LI class="L2"> <A href="/part5#5__information_disclosure_config_1">Information disclosure #1</A></LI> <LI class="L2"> <A href="/part5#5__information_disclosure_config_2">Information disclosure #2</A></LI> <LI class="L2"> <A href="/part5#5__information_disclosure_bug_3">Information disclosure #3 </A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__ajax_vulnerabilities">AJAX vulnerabilities</A> <UL> <LI class="L2"> <A href="/part5#5__dos_via_ajax">DoS via AJAX</A></LI> <LI class="L2"> <A href="/part5#5__phishing_via_ajax">Phishing via AJAX</A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__other_vulnerabilities"> Other Vulnerabilities </A> </H2> <UL> <LI class="L2"> <A href="/part5#5__buffer_and_integer_overflow">Buffer Overflow and Integer Overflow</A></LI> <LI class="L2"> <A href="/part5#5__sql_injection"> SQL Injection </A></LI> </UL></LI> <LI class="L1"> <A href="/part5#5__after_the_codelab">After the Codelab</A></LI> </UL> </DIV> <DIV class="column2"> <P></P> <P></P> <P> <NOAUTOLINK> </NOAUTOLINK></P> <STYLE>.column1 {display:none}</STYLE> <BR><P> <H2><A name="0__hackers"></A>Want to beat the hackers at their own game?</H2> <UL> <LI>Learn how hackers find security vulnerabilities! <LI>Learn how hackers exploit web applications! <LI>Learn how to stop them! </UL> </P> <!--MARK-0--> <P> This codelab shows how web application vulnerabilities can be exploited and how to defend against these attacks. The best way to learn things is by doing, so you'll get a chance to do some real penetration testing, actually exploiting a real application. Specifically, you'll learn the following: </P> <P></P> <UL> <LI> How an application can be attacked using common web security vulnerabilities, like cross-site scripting vulnerabilities (XSS) and cross-site request forgery (XSRF). </LI> <LI> How to find, fix, and avoid these common vulnerabilities and other bugs that have a security impact, such as denial-of-service, information disclosure, or remote code execution. </LI> </UL> <P> To get the most out of this lab, you should have some familiarity with how a web application works (e.g., general knowledge of HTML, templates, cookies, AJAX, etc.). </P> <!--MARK-1--> <BR> <BR> <H2><A name="1__gruyere"> </A> Gruyere </H2> <P> <A href="/static/gruyere.png"> <IMG src="/static/gruyere.png" height="285" border="0" style="float:left; vertical-align:middle; margin-right: 10; margin-bottom: 10"> </A> This codelab is built around <B>Gruyere</B> /ɡruːˈjɛər/ <!--groo-yair--> - a small, cheesy web application that allows its users to publish snippets of text and store assorted files. "Unfortunately," Gruyere has multiple security bugs ranging from cross-site scripting and cross-site request forgery, to information disclosure, denial of service, and remote code execution. The goal of this codelab is to guide you through discovering some of these bugs and learning ways to fix them both in Gruyere and in general. </P> <P> The codelab is organized by types of vulnerabilities. In each section, you'll find a brief description of a vulnerability and a task to find an instance of that vulnerability in Gruyere. Your job is to play the role of a malicious hacker and find and exploit the security bugs. In this codelab, you'll use both black-box hacking and white-box hacking. In <B>black box hacking,</B> you try to find security bugs by experimenting with the application and manipulating input fields and URL parameters, trying to cause application errors, and looking at the HTTP requests and responses to guess server behavior. You do not have access to the source code, although understanding how to view source and being able to view http headers (as you can in Chrome or LiveHTTPHeaders for Firefox) is valuable. Using a web proxy like <A href="https://portswigger.net/burp/" target="_top">Burp</A> or <A href="https://www.owasp.org/index.php/OWASP_Zed_Attack_Proxy_Project" target="_top">ZAP</A> may be helpful in creating or modifying requests. In <B>white-box hacking,</B> you have access to the source code and can use automated or manual analysis to identify bugs. You can treat Gruyere as if it's open source: you can read through the source code to try to find bugs. Gruyere is written in Python, so some familiarity with Python can be helpful. However, the security vulnerabilities covered are not Python-specific and you can do most of the lab without even looking at the code. You can run a local instance of Gruyere to assist in your hacking: for example, you can create an administrator account on your local instance to learn how administrative features work and then apply that knowledge to the instance you want to hack. Security researchers use both hacking techniques, often in combination, in real life. </P> <BR clear="left"> We'll tag each challenge to indicate which techniques are required to solve them: <BR><BR> <IMG src="/static/cheese_b.png" style="vertical-align:middle; padding-right: 5px;"> Challenges that can be solved just by using black box techniques.<BR><BR> <IMG src="/static/cheese_w.png" style="vertical-align:middle; padding-right: 5px;"> Challenges that require that you look at the Gruyere source code.<BR><BR> <IMG src="/static/cheese_bw.png" style="vertical-align:middle; padding-right: 5px;"> Challenges that require some specific knowledge of Gruyere that will be given in the first hint. <BR> <P style="color:red"> <B>WARNING:</B> Accessing or attacking a computer system without authorization is illegal in many jurisdictions. While doing this codelab, you are specifically granted authorization to attack the Gruyere application as directed. You may not attack Gruyere in ways other than described in this codelab, nor may you attack App Engine directly or any other Google service. You should use what you learn from the codelab to make your own applications more secure. You should not use it to attack any applications other than your own, and only do that with permission from the appropriate authorities (e.g., your company's security team). </P> <P></P> <FONT SIZE="+2"> <A href="/part1">Continue >></A> </FONT><BR> <BR> <P style="font-size:x-small"> © Google 2017 <A href="https://www.google.com/intl/en/policies/terms/">Terms of Service</A> <BR>The code portions of this codelab are licensed under the Creative Commons Attribution-No Derivative Works 3.0 United States license <<A href="https://creativecommons.org/licenses/by-nd/3.0/us/">https://creativecommons.org/licenses/by-nd/3.0/us</A>>. Brief excerpts of the code may be used for educational or instructional purposes provided this notice is kept intact. Except as otherwise noted the remainder of this codelab is licensed under the Creative Commons Attribution 3.0 United States license <<A href="https://creativecommons.org/licenses/by/3.0/us/">https://creativecommons.org/licenses/by/3.0/us</A>>. </P> </BODY></HTML> |
| URL | https://google-gruyere.appspot.com/%C9%A1ru%CB%90%CB%88j%C9%9B%C9%99r/ |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 395 bytes. |
GET https://google-gruyere.appspot.com/%C9%A1ru%CB%90%CB%88j%C9%9B%C9%99r/ HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: https://google-gruyere.appspot.com/ Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 263 bytes. |
HTTP/1.1 404 Not Found
Content-Type: text/plain; charset=UTF-8 X-Cloud-Trace-Context: 93614a44ddbca5487eb80d4bc08e09da Date: Tue, 06 Feb 2024 17:42:25 GMT Server: Google Frontend Content-Length: 52 Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 |
| Response Body - size: 52 bytes. |
404 Not Found
The resource could not be found. |
| URL | https://google-gruyere.appspot.com/382665580745386307547168512335551204731/ |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 451 bytes. |
GET https://google-gruyere.appspot.com/382665580745386307547168512335551204731/ HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: https://google-gruyere.appspot.com/382665580745386307547168512335551204731/quitserver. Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 306 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: 1b18c15e61fbec8b3e4949b4ddc0dc67 Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 3480 Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 |
| Response Body - size: 3,480 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Home</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> <script src="/382665580745386307547168512335551204731/lib.js" text="text/javascript"> </script> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/382665580745386307547168512335551204731/'>Home</a> </span> <span id='menu-right'> <a href='/382665580745386307547168512335551204731/login'>Sign in</a> | <a href='/382665580745386307547168512335551204731/newaccount.gtl'>Sign up</a> </span> </div> <div> <h2 class='has-refresh'>Gruyere: Home</h2> <div class='refresh'><a class='button' onclick='_refreshHome("382665580745386307547168512335551204731")' href='#'>Refresh</a></div> </div> <div class='content'> <table width='100%'> <tr><td colspan='3'><b>Most recent snippets:</b></td></tr> <tr> <td> </td> <td> <b><span style='color:blue'>Cheddar Mac</span></b> </td> <td width='100%'><span id='cheddar'>Gruyere is the cheesiest application on the web.</span> <br> <a href='/382665580745386307547168512335551204731/snippets.gtl?uid=cheddar'>All snippets</a> <a href='https://images.google.com/?q=cheddar+cheese'>Homepage</a> <br> <br> </td> </tr> <tr> <td> </td> <td> <b><span style='color:red; text-decoration:underline'>Brie</span></b> </td> <td width='100%'><span id='brie'>Brie is the queen of the cheeses<span style=color:red>!!!</span></span> <br> <a href='/382665580745386307547168512335551204731/snippets.gtl?uid=brie'>All snippets</a> <a href='https://news.google.com/news/search?q=brie'>Homepage</a> <br> <br> </td> </tr> </table> </div> </body> </html> |
| URL | https://google-gruyere.appspot.com/382665580745386307547168512335551204731/feed.gtl |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 412 bytes. |
GET https://google-gruyere.appspot.com/382665580745386307547168512335551204731/feed.gtl HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/part2 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 305 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: 0d6b4d9f18f7cecd56fd804c68263bbf Date: Tue, 06 Feb 2024 17:42:23 GMT Server: Google Frontend Content-Length: 191 Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 |
| Response Body - size: 191 bytes. |
_feed((
{ "private_snippet": "" ,"cheddar": "Gruyere is the cheesiest application on the web." ,"brie": "Brie is the queen of the cheeses<span style=color:red>!!!</span>" } )) |
| URL | https://google-gruyere.appspot.com/382665580745386307547168512335551204731/login |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 456 bytes. |
GET https://google-gruyere.appspot.com/382665580745386307547168512335551204731/login HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: https://google-gruyere.appspot.com/382665580745386307547168512335551204731/quitserver. Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 306 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: 8b42cbce881c35ee8d87883b85cd8f7f Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 2591 Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 |
| Response Body - size: 2,591 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Login</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/382665580745386307547168512335551204731/'>Home</a> </span> <span id='menu-right'> <a href='/382665580745386307547168512335551204731/login'>Sign in</a> | <a href='/382665580745386307547168512335551204731/newaccount.gtl'>Sign up</a> </span> </div> <div> <h2>Gruyere: Login</h2> </div> <div class='content'> <form method='get' action='/382665580745386307547168512335551204731/login'> <table><tr><td> User name: </td><td> <input type='text' name='uid'> </td></tr><tr><td> Password: </td><td> <input type='password' name='pw'> </td></tr><tr><td></td><td align='right'> <input type='submit' value='Login'> </td></tr></table> </form> </div> </body> </html> |
| URL | https://google-gruyere.appspot.com/382665580745386307547168512335551204731/newaccount.gtl |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 465 bytes. |
GET https://google-gruyere.appspot.com/382665580745386307547168512335551204731/newaccount.gtl HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: https://google-gruyere.appspot.com/382665580745386307547168512335551204731/quitserver. Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 306 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: e1f0d836f241aeea4e12446db611b471 Date: Tue, 06 Feb 2024 17:42:24 GMT Server: Google Frontend Content-Length: 2961 Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 |
| Response Body - size: 2,961 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Profile</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/382665580745386307547168512335551204731/'>Home</a> </span> <span id='menu-right'> <a href='/382665580745386307547168512335551204731/login'>Sign in</a> | <a href='/382665580745386307547168512335551204731/newaccount.gtl'>Sign up</a> </span> </div> <h2>Gruyere: Sign up</h2> <div class='content'> <h3>Sign up for a new account.</h3> <form method='get' action='/382665580745386307547168512335551204731/saveprofile'> <input type='hidden' name='action' value='new'> <table><tr><td> User name: </td><td> <input type='text' name='uid' value='' maxlength='16'> </td></tr> <tr><td> Password: </td><td> <input type='password' name='pw'> <br><span style="color:red"><b>WARNING: Gruyere is not secure.<br> Do not use a password that you use for any real service.<br> Do not upload any personal or private data.</b></span> </td></tr> <input type='hidden' name='is_author' value='True'> <tr><td></td><td align='right'> <input type='submit' value='Create account'> </td></tr> </table> </form> </div> </body> </html> |
| URL | https://google-gruyere.appspot.com/382665580745386307547168512335551204731/quitserver. |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 415 bytes. |
GET https://google-gruyere.appspot.com/382665580745386307547168512335551204731/quitserver. HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/part4 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 306 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: ba514b4b962961f93b1c4fbc21fab959 Date: Tue, 06 Feb 2024 17:42:23 GMT Server: Google Frontend Content-Length: 2246 Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 |
| Response Body - size: 2,246 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Error</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/382665580745386307547168512335551204731/'>Home</a> </span> <span id='menu-right'> <a href='/382665580745386307547168512335551204731/login'>Sign in</a> | <a href='/382665580745386307547168512335551204731/newaccount.gtl'>Sign up</a> </span> </div> <div class='message'>Unrecognized file type (/quitserver.).</div> </body> </html> |
| URL | https://google-gruyere.appspot.com/382665580745386307547168512335551204731/RESET. |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 410 bytes. |
GET https://google-gruyere.appspot.com/382665580745386307547168512335551204731/RESET. HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/part4 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 306 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: 07f510b6cdd84a858cf4d3c73829ba7a Date: Tue, 06 Feb 2024 17:42:23 GMT Server: Google Frontend Content-Length: 2241 Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 |
| Response Body - size: 2,241 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Error</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/382665580745386307547168512335551204731/'>Home</a> </span> <span id='menu-right'> <a href='/382665580745386307547168512335551204731/login'>Sign in</a> | <a href='/382665580745386307547168512335551204731/newaccount.gtl'>Sign up</a> </span> </div> <div class='message'>Unrecognized file type (/RESET.).</div> </body> </html> |
| URL | https://google-gruyere.appspot.com/382665580745386307547168512335551204731/saveprofile?action=update&is_admin=True |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 443 bytes. |
GET https://google-gruyere.appspot.com/382665580745386307547168512335551204731/saveprofile?action=update&is_admin=True HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/part3 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 306 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: 49fab23ad5f50929949b343099495117 Date: Tue, 06 Feb 2024 17:42:23 GMT Server: Google Frontend Content-Length: 2228 Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 |
| Response Body - size: 2,228 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Error</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/382665580745386307547168512335551204731/'>Home</a> </span> <span id='menu-right'> <a href='/382665580745386307547168512335551204731/login'>Sign in</a> | <a href='/382665580745386307547168512335551204731/newaccount.gtl'>Sign up</a> </span> </div> <div class='message'>User does not exist.</div> </body> </html> |
| URL | https://google-gruyere.appspot.com/382665580745386307547168512335551204731/saveprofile?action=update&is_admin=True&uid=username |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 456 bytes. |
GET https://google-gruyere.appspot.com/382665580745386307547168512335551204731/saveprofile?action=update&is_admin=True&uid=username HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/part3 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 306 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Content-type: text/html Pragma: no-cache X-XSS-Protection: 0 X-Cloud-Trace-Context: 1c2eebd839ba8c29a02e769d0f003a2e Date: Tue, 06 Feb 2024 17:42:23 GMT Server: Google Frontend Content-Length: 2228 Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 |
| Response Body - size: 2,228 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- Copyright 2017 Google Inc. --> <html> <head> <title>Gruyere: Error</title> <style> /* Copyright 2017 Google Inc. */ body, html, td, span, div, input, textarea { font-family: sans-serif; font-size: 14pt; } body { background: url('cheese.png') top center repeat; text-align: center; opacity: 0.80; } h2 { text-align: center; font-size: 30pt; font-weight: bold; } td { vertical-align: top; padding: 5px; } a, a:hover { text-decoration: underline; color: #0000bb; } a:visited { color: #bb0000; } a.button:visited { color: #0000bb; } .content { text-align: left; margin-left: auto; margin-right: auto; width: 90%; background: #ffffcc; padding: 20px; border: 3px solid #ffb149; } .menu { text-align: left; padding: 10px 20px 35px 20px; margin-left: auto; margin-right: auto; margin-top: 20px; width: 90%; background: #ffffcc; border: 3px solid #ffb149; } .menu-user { color: #000000; font-weight: bold; } #menu-left { float: left; } #menu-left a, #menu-left a:hover, #menu-left a:visited { color: #000000; } #menu-right { float: right; } #menu-right a, #menu-right a:hover, #menu-right a:visited { color: #000000; } .message { width: 50%; color: #ff0000; background: #ffdddd; border: 2px solid #ff0000; border-radius: 1em; -moz-border-radius: 1em; padding: 10px; font-weight: bold; text-align: center; margin: auto; margin-top: 20px; margin-bottom: 20px; } input, textarea { background-color: #ffffff; } .refresh { float: center; width: 90%; text-align: right; margin: auto; padding-top: 0; padding-bottom: 2pt; margin-top: 0; margin-bottom: 0; } .h2-with-refresh { margin-bottom: 0; } </style> </head> <body> <div class='menu'> <span id='menu-left'> <a href='/382665580745386307547168512335551204731/'>Home</a> </span> <span id='menu-right'> <a href='/382665580745386307547168512335551204731/login'>Sign in</a> | <a href='/382665580745386307547168512335551204731/newaccount.gtl'>Sign up</a> </span> </div> <div class='message'>User does not exist.</div> </body> </html> |
| URL | https://google-gruyere.appspot.com/code/ |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 369 bytes. |
GET https://google-gruyere.appspot.com/code/ HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/part1 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 303 bytes. |
HTTP/1.1 200 OK
Cache-Control: no-cache Pragma: no-cache Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: e9bcd98bd046d94c037b912b5eabbbf3;o=1 Date: Tue, 06 Feb 2024 17:42:22 GMT Server: Google Frontend Content-Length: 354 Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 |
| Response Body - size: 354 bytes. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN"
"https://www.w3.org/TR/html4/frameset.dtd"> <HTML> <HEAD> <TITLE>Gruyere Code</TITLE> </HEAD> <FRAMESET cols="165,100%"> <FRAME src="/static/codeindex.html"> <FRAME name="codepage" src=""> <NOFRAMES> <A href="/static/codeindex/html">Code Index</A> </NOFRAMES> </FRAMESET> </HTML> |
| URL | https://google-gruyere.appspot.com/gruyere-code.zip |
| Method | GET |
| Parameter | |
| Attack | |
| Evidence | |
| Request Header - size: 380 bytes. |
GET https://google-gruyere.appspot.com/gruyere-code.zip HTTP/1.1
host: google-gruyere.appspot.com user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 pragma: no-cache cache-control: no-cache referer: http://google-gruyere.appspot.com/part1 Cookie: GRUYERE_ID=382665580745386307547168512335551204731 |
| Request Body - size: 0 bytes. |
|
| Response Header - size: 341 bytes. |
HTTP/1.1 200 OK
Date: Tue, 06 Feb 2024 17:42:23 GMT Expires: Tue, 06 Feb 2024 17:52:23 GMT Cache-Control: public, max-age=600 ETag: "3m8CBg" X-Cloud-Trace-Context: 5eb2a20493dac6f4be23246df13adf7b Content-Type: application/zip Server: Google Frontend Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 content-length: 35401 |
| Response Body - size: 35,401 bytes. |
PK
K×Je!¼9 Á data.pyUT RLYRLYux § µVmoÛ6þ®_qñ>¨Ej)éPtðn¦¤]Å0´x¶J$Ë8ÂÐÿÞ#¥$²c£° dñÏÝ=¼ã4C8ÃóÎ
2Ð^ Îi]9¡$¬+¡TÚ,INnX^¾¥Â¥,2WDÍ-rÚ0-
Bqz&J¼ähÀ¥sÚò¼0Hæn±Pu¤ÍYæºÍçÍPòü×ì ÷6ON;U8muaìs¼^+
ÎÛVã³2_-ÐFø$ #ÃÇ>8BþÝÀõÍNo>~éûË ý:;ßëüîÓ>ÁÖ¢´X- I[ ¾F£¼M,Þ×tÂ¥PÚ¨[Áis ß[l7B>Ú¸v~§+%T&yBçVlÁå¡f ÔE"Û·QÞZÉÖ@)褸r´· A¤¢¶e#XÄähÐÏภ¼+ÐhòI-HIØæµa!÷m¬ú}Ñ-ho´ <0È:ÀÐWÔÄq\82Ûlðü> ÇliTÑÏ):ú3¡Ì#ä>±V«U¶iQnåXå(saA¹åÂÆ<I21ïJef38ô-yðçL¦IòØÆ:¬#N2µVÆ/º¡4:7þôa:;OÇ´÷ïèI¯ |2§L:ê£H²i%ôÅ£@¯Â2eA×_¶sÄïXeqCqF05¾/ѱpf¥Ð úªÍ¬])ÃÏ¥.èvoú&)±gÊ&ìÚAg¬Âåݶïí+-Jäíù´Â+®KÜøFt½¸7)Ùødrb>bP\vDÂþÒN¢|óíøàõoÃWÃÃÃÞÇQÎN²Q)j¶¤Ð³Ksûô¦&Ú(T9Hç_t!Zý¹ævz_-rlrÞµ+úBò°ïqÜ +;µuXø ¬w5ªÓX¾=éÄ_ë9aáj{F\ kaåOfÿ|¸V ¦®eTþP5*û5ð MãË©öc¬§gï:¼ ÂæÔk·óºðS¦þ|CòHHKN<vÐèÃL$YQ(/ý)Wki¾YÜ¡ÏÜïàðÎ yà9fï(Þ$*Ãÿ¬"s÷åðÍ#ÊvuµöÈj&Áº¦ÂãhxDVOöööò 9ÙÝß$á4»KÕÝ©=fÊÇvºYà;n\Y<@7&çÃ$ã:üxÖ*ÏPK }¡×JÔæ} l gruyere.pyUT néLYnéLYux § Ô<ksÛFßù+&vù l(PÖ^URÊr«d¶Uq$D]6¥¨X 0$ Lq½þï×ÝóÀ ôJnËç¤Jä<zzú===|ùͨÅh§#~bù¶\eéÿÝ`ðâÅwEµåg{,`>gA'qq²M\®Ø*K¸ðã,ßñrU²ý×ß±wY¶L8;MC% £.Á .xñG0aº³3ø0y*xĪ4â+W|°*Ë\FaÁa¹O<ÌÖë,~V,Gj¸Í·{i4ú«¿[ ÕPv,Dz£²,ây Øîeìñ'9âç¬ø(Ld×i\ÂÂWePràwrÎÎΧìøüâ6}zN&ß(¼m2àW&ÖìRðd±fiÄ)Àì$XZyUb xXq¹U$d§%ÊìSÁäEV°Jp9 Â~hG´É]d15H£Á _E¢I0÷Ù/YÅÖÁ³(^l Qán³ª`Ù&¬bàTÅé¯ 0 $ iV²(ÜÅñ>{SÄ|Áø]ÈpÊ0( Ì9.&·Å£Jî!H|S \ ª!¯<ÃJ À XúÈs Ð8,aÙ-E̵ "ÆÁ²àdFxNy±&|®@ò ¬Ífã/IL}@L@FyA¶J8B%f³ µ(f36fÎÀ³|¤Î`ð(ù)R'"[³7àï§Ó\$:^çYQÖKþ{ÅEùXðâ¾uË@µËØ|¼Ã ×_3¡? 8[ëoþX®@G" n¨$çøDÝGß²Øò#ïÙÏæÛÇlZT|ÜÞhã¢È B ¡Pì4ÔQPúó²L xofoO?LÐ#Qföp_ÞÎàjr|9Z8ØRö NÏ®¦G>Ì.¦ï±×w«óëËãiX\Àø«ÉñéÑÙñùù§ofÙÇ;¦ëâò\¯5\ªÔ'o¨=[ã.~ºÃ"Xºëúìô×Ùé õVi"0#ÀC®?»]¶©¦£Nϰ1³ ZÇiÝs=}~©»H8Ðø(úÈü¨ã¨BÊ´ñ»¸dÁh!î3°HOóþà÷*.gjÖ½ ì-lÉÎs`&PëÍÞñòD7AÊbqé$Wg8 e)ÁKþ³ùo4ÔµÆB>bóìSTÊ´ÀÎ<år¶ Ä l4ÁAJAW³¤Á:g.ýl)p@ORé·hù] F5FOÛÔÃPÜåCs-Óø_à3°M'gÓÙô ÃgÐò á2§À#ü<TíËxíñ:Xò~Ñ«rm&ÀçÄîHº{~£%,·3ú-ø°ó²óe½"}«»úzòÔêÁ/º#³ºãn¾¦¦4æ 8ƺ«£çÈpd _]mǽ50ë$bìl]o =J-1 ÆN¡eì%HN>z;4®"K-%:,À9åäPjF$%IÉ*àgNèYÐ&« ¤_ 2Ûhá¦YèïQ2®lH^ \éç¤,Âv°PL@¾\*j2øFja×´Eعlx¦Á;"k61âCºjðÒÊ+1^á ÂAÐôÎ@Mã\÷Ï>²PÂ?]½ûéÐéýÑÙ» OööüÃó¡±ó·: ©]App*HAN0/R|äH&èÄXä!¨4ÓR¯ãvUÄâ£äH J £b<ÒK@÷µGAê0¹cô¬i¦MÀÛÄÝaaäuç¾^àÀCÁ 4|Ü`Ì! fé^¨é°µüDYr>Ê)òÏLø5À²Ù»Ét.ί¦¾§Qø+ ¢%E4(*`T¥%Ænj8¬RD@Ô%ADXÜ·P³ª¤0Ííªp #¾ %@|C¼K°!êW $"¦ Á'4hP<CÄÿöØQUfkØv_ öU!ûÝÁþ>jOFÂCé)¨\Á2@ hÀ:BÀ$VS~O¢JSr Úñì ü)¶ÐrCëy²Ù0rìB8Çp±4 ú¢R»8ìñÿÚ QMu<d"¶IBÌÈ! ôs0R©ëhëâx>RÂÅ?`³Cî:¿Ý¯éuä`ÕÓ0t^|çïïçTà(þ³ï÷÷¿ÿ£\ï@PWèkt_]\½CǸý¹ôAqî_ѹä¾<NÚ:8(ø,+Ok#ûçb¨Ôåü(ÕlfC&ã,Ç{$ÁU 8iëçqÒÁ Õ-|¤+Ý¿üåõÁ÷C]øðíkÏóÉÙqÒVtÍX·]×ú¡-³¦zþ©H§yÚ¤Ø)à·dÿ;èDñ¢@vyì|ß·VA$áøüJ¾F}í¯ÄÀ±WÌmá×·aO7Ã;´NÈÇÆ9cö!"}$¤L} Zaã!§N¼zcªÏ_ gʺ2» >qÛZWPJô#ßγ NA¢ÊK½B'7~M/µÝ¨Õvé>èwdxÐsæ¢Ò¡Ó~Z°O 岬ÈROÆQx¼?1#¯lŸ8µÍ¹>¢%R¢¨²pGýØâJp:`ß2¹Ô˸ûÎÃ"öâÅ: )ÞÈ,Ò#tj¨nP9; xÄó1óCN1à åfÿVr W0ÛµzJM1S¸`có L¾ ß ÀãU ¹ºæè¾8XBº@ù>GOmç@LåR¤6ÕAå|î.ä %JfÝÓs²öCö?xÒ¦Ï\vºTf<ý¸Ë0lÊòÈ.1ò1ãÇ*uºð'üþ-Y ¯VüÆ)Û§ú - Dç´¹z£ð¥ 7l®VÃf1hlòÏf\=*¥ 8»¡û,Ù(zjÎFÕ:ob=dýV|îf-B!,kK@¨ài$CÒiº1;ÅÜÑ4ÂàÜqB¦´h¤IzôTfb¤kÒËË ªL=FÈÙYëL8E·Þ®ÞItuJ²ÃJfï,TsóH¢0 à8ßéÝî$°¦ü 4B\¾ì(U§()áÐvvýÁJa~®¤KCfçå½n¤4¢jý"¸.ÞÂeI̱Tz¾eÄë ¸BÇ]LNáh?9]_~À×rQÎ34_ñØ-.G²då ]¼CQïx)*¢¬rÇ'sþÜjÖN¶ifH9Oø"¨Z[¹ò.[ ¼D¼{Ф¾n<_öaú >zá´1NiÉ¡âûPªpmÈd¢¹FFJ$Û(Á©Ï=ÄTiÑ-öK.èÔ-'x%A2ØÊ¨©Ô-ù\¥PMe Ö!;§¤ú.ó´¢wÜÃ!;^Æò3HGøzKô×XÉ1]´ù ±ì[K.Ó¸J(¡)¹íÙÉ7VÞf%C} ûBãáÊaC JÇ| '`îEs©Bn¼A·7¸à-߯xV´ëj^§|£n&f-õ ËݸáÒ (?¦¡®óîòúÉåçç Ú£O²÷Ù»}µ»®A*j°È~Iñv¸ ûÂ"ßQ** ¹¤ÜÕÀg¦ !½4äAôO£)À6 jg)pßrî/ËÄéP¦*eUùl]ÂD)Sð êZõ«VÇIÑýòD ÐsE§fÁ³¿å` «&S:ò¾ZÚ?X6mhàýôÙ¡í¸cù"¬¡hQí«£k1ºôD»o¬GóJ-È*«ßK-yéÖ1±¿;ÕMðòÕäãq®sS¸Ugþ¤vfSÝ@¥\Y8£¨ÎÚcéîÕ»7A¡.<µ>Ecm×(Þ)^Ûâ1ÙÎñ¡vÌáªRâ,xþW¨²È:¹Æ£)Xì ÄQлéÿ!°L¥<Ïóünæ#ÁäJ#Ç#]ÓÔ·ÏTyæV)^§{Ï$u{~ÍÃZA}óJ·3ÍÕV»²TÝiçeqEuè%!nZP»gQCñxËÀN 4±*?p©H+Ðã=Ud@÷hzÇ&é¾Þ/X$¹,ÄÃ Æ 6£Ù#å×ý[ee0ÔËbV+Ķl½Úô0¶èÒM:õþ%xÌ>Õ«ksª«ã«4ÎsÀ Wä¡fLÇZÛ5Ò¯¶uz ãTG0¡ úOp:VXY¾áf×/ÜÖ<è¥bJ$°ú$ nê¡ÈÈ O·¢ì´Ðî|«²yÓmÞHæéµ1àRIÆkrtöyY³LxsÕzð<Ö dÈeE7·ºAu½ÑÄï3¥¥H¬ó²Ñm§té!ß8÷¹ H¤Q¦]0hjó®ÝaijØ>^J¥»?ÔMíèæR]ûùÍLj47;Us·¶?=á QÍ}xE6)`²ºc¡ïàtúµáùÑk ÿwÌÈ@¸ÁPí5mÌ)¶µLÇõÁ®T[éuÉ º¯GA}6n=ïþF¦âO@Lü+kûhùÃ9Âö: ÀW%]Ô}*£P¹5Ç«`Xg JUáªr@¯à)öHÀÐËÅ¥Cy@V 2Áªê¥ J»À99UKµGµ0P.Ê©ø¹IÖû^>ãÒZµTj^ÅX¢ Q ;ÖÛÏ_Oj1Ù=/Â*ù= ´¹Ê:TSo62Ø;3ßôÌëR?g&U2?ubN7øLûË'/Le©O´áóKþäaÜC%_ \±£L*°ÄªeYÛ 2K± ÿu¥ÜÓ3á&Vk¥ì.¥q÷K2&NÑÆÒ>"/n%¯;_5ØKSßt"]ªÒH¶×qp}ÙòP"ôáôy×Â0«ÒR×wù9Ú9Yú·bõjEWiõùÍ"¤²w-ZªÁÃôlÙ³ Èb×ÝÄ7÷3?K"c}ô? ífÒè®Ñ³.3^²u%dù,*?J´¨òy Ì -|ª £õeA×&OÓ0+P ÚW mÁ0·k_§<U|ÉwWáeX!¡8;vÇrÀuÃ~´VAÄ+ 2D9*½äbÁÝJϤ¨/ ´çMT-}qê~BJ¹ãà°:i <2f¹ïËubò42lÂÇ¥´qªÌ£0O2J;XÈ:±#+(ã*=û ie;2 PiHîÁþ¾×ê`1EZò´Ü+!$ÆÙúåFß"X®f{!¸ n[¸Ú\j ÛÍéáòîîýóêjïÂ# û6õ`áZÍ fém;kò¾£¼G$Çû Ì!¼\Ó¤w&Z«´!dÎßòÿËÈ õ¹»ÒêË¿¨÷Þ"ÊÞzg^n¬.ÙQµûz$)ØË§>²HL:D¾Y£Z1ÿùN ZØ!uì¬KÏùj2k«l¶¦¾t.âGW]6j zÅÇpHìOîò f¹«Wë8òÕ\¾Wz°©Wrq4f1Æ ^ô¬çLÙ:!ÒèëºQ¡¥´%Ù¦¶H Ñw:¶w¤v4³_'[,ÃÈÕ>ͪ7fù$*rÁï{:ù¡ bH¡§ÞOzo&`aoR¾fz}þoÐÀ~$WÇõ¢ ¢ûña¼îÁ7àí=þ7WsG+\6>emé¸y¶e Ï ;îq×c?ÂgÛDÔ^²£d7¡ä ±b¾CóVTôÆáô[Ei''¯æIÙ:¸Û`mO~Ôîóѽ^ºÃOk;ù(CiÛ.)Òh==ÀìöliëðäVæe2uø¶£ëªúÐ0Y0 Lºöÿ· ÿ'þñÕHYO+!hí¤§z´çÃÒãVYaM#̾õ¦<gÒC*ȳµ_èIÆY@ »÷óÕpíªX^Ê´ÿ Ë_ëà A>+ÿâßyV*©r¸¥Þ«·¼øuP¤Â×l³â$.4®læÅ@°´Z½VWÄ2³ó±tG0">9SßÞ UÄËT=ÇÄêá@¬Ø¢JCY]ݸ3P Ѹ¿Q7yÄWߪ)nðcæñTÆ1)kódTÇõ(°ÍÕ°Jc¡Y´¯oa¤îA¨ò í§ÞTrw¢1¿¶Ð^q¤< íªkØZ»èZOÛ©»6èÊ$r¨ÊÎ+ñoúü'A¬aI&Øõ âYºOoòLÄô³7$ÒfÑÌ^ó Øé6N|«0òرý»ïÞÒ?õÊ@'Õ1¡jäQnÊBVEË[8T <û¿ÏÈØwùAºÇB|n,Ëzé×JU9«áÚà.ÍE_ˤK(ìרÅ6KèªÁºf°JÏüm.ð×5p0¥|éM61Vä£2¶aùJ±tNXéMB øö*HC]g((YddMUq¢êõÁ%â+¯ìc°|DÊj þ G| =;áÖ}¸S,¬ ] ´æf ~Ëé°±épB þd¡°_Ø5ÍèaQ%ZéUµI·¼n)²ÍõU`PÖ óU+æpñõ6Â|ݤ.<Sáèã¯3å®§;óöÚº»SLñÚ´Øcì®ÜëÛZ02¬²Ym³ªæÎE[cI9 U§c?m!â8¦ñ;S²M-þuñäÈ·© .NwØßÅú©ÈH¡45W$7þmsãeß«5Å* 2ò½fÙ^̶Í;\ÝÑ#Ù«ö8¶!ÕȶÊ}>[ºl{: äfÿv¸;@y>5ä5=~P^µk´òjø®5þË3¤³×dôhçÁË{dWÄçÕÜéôvô& z4ï,-¡!÷j»¯ þÖjJkÞ|å=¦.Sm?3u=ßß-Ö<É®s|Vú̲JNÕ=äü)=ÙN¦Fè_[!F7Ñ,V}'eõä¤,,1ø¶ÈÖWêõ+ÊÚ¯äxmX?î*Nè»NFù~(+cÖldd6uFf¡Óí¶fvÍ,ý&ýæo<`? ~ÖH ;§ý£ôûw jÐ÷^euÕn´sÐÍÇ´CøÞq!X¿«¥ÍË ¨+ËkRñ;_ßöÙU¦íÏM(ui'ÌÏPÕ9DÚ~©u´Gè$Õ aTMÿþÖE£Gú.©o^À)Ù2ö߯<®@è`óõîùP zK*¦ÛvÝËÓOq¥ãÏÎåàj:ûi~ê¶f©ëâȱSª0°ºë^hç½/3uZÆ]Ý8V*çµËþ·¶sëmÛð» R`zV·öÉpóÐ8» ÙZ´ÝúȵV9Z%Ù]÷ëwn$%*±]ï¥eñ"¨sãgõíþQyãcµÏ饳Û+¯»=:ëÑ;´ïe- AØ®Äm<ËpÓ«\©PbÖí¸È9_Jz¿muZîT¢L Z ]z%ôOí w¼=ÖùFnE¼ùâ@ÆN¬ ÍèK· :°YWåxÒAAüê_3¶ ×å©fkߥzK¸oxºQoôrtiNvûpê¥ý EùÇû8 þ¥½J|{:lDWªb9:K'{ùüÙ¶x¨Ç÷ \ª$¶TÅ·ÛðɰøcTuBÅIlCFú´ljJàeưT ãyØ ÿ}# öhÃ=Åã}¥0,}ùø+¹Å÷àrSÔ"¢$À½iâxv6}+dôËûß®¢7¼ºúõ"¿OÏ/dþ~N_$Éåïñ¹m8Ã>ÏåÝWu°É";Gµ28Kø=Ç|^Õ,ÖU¶zùä»úÉ9iÙ,I°Ïh4Î)¢0höÈS »á'³ª½u£FH!x¤ ¬-A6*ؼ¨5Dùaz3¹MÿÎðxÿÍL¦³ÛvÊ¥£Â72>öô&Q \9«V鲿%ÜíëêMIæ¤üóåÿ WôÍ?a'ñ1`4i9ò 6wbD¯ûÖGhÓÀ9àMS60[ùkðBe÷YF¯ <ôâÆ\OÄ=(Úy¬ Ò³ó¤âj» `Td¥ËrMι!ð&]Qa:7øâØm¤î.¹ÀmFu)²*'è?v¥\¹^%µDÌ\{©ÊzI5R OB¬ï^6S¸¡Nðè!^GQìL!æê ðâò²Ñãõ2ºV A«GO÷G¾Ë2bá]uKG^køÙÃqenéíkA+!w áLÅÐó./·¸¯¥ZìR¦ùï{EZ )Ä ¯Ä§n¨TËaY5JÙ9 ÒÑlÙÒ3£yú¬TI6ó®Ñäãd,¿J@\ R mæ¥ZAñÙv2£!Û 9*ÆWÌùa¾Ä¢Rï·eV&nÀ¾§zB¢êáLU±æè Ú&p[äXØ Éõ³ýiùJËzõðLACipÃã8blÌñÀÛ³HÞ¬ÖËÃ3Ý=$L°xÐÎ÷·h4hÃeÒlÿÄ©<ô>9û ÏÐìTs¦ £½ºøÄ'Ó[äSOà«xÜÅ8î#L¬o§¤¦íëÙQzÛ¯µ½5«uTX{ÉryHÖÛ»}õ6¨µbáÿK>N{2& ¤u¨¢,ÙÆ¶ÁÆaö'~¸Ô-é ÂØ ½°·K®eÁè¨.Òz=±Î¿ØÍÈ`[VÓú@¶UZ}zÔWµ ÏEm²E¾lï?oKÜÌ@®vbéæ>¬Ý=¡kq,ëÅçzH7Mhhì }òbnÿ\+çìj ì>Ñii0³æ¯4Üep¦ ééÿHC:ÏrgE3ò^£sT÷ñ{þÎk»éÝSÞÞ§2Þ·B³ÌOÍPÛQvþ'§Á`@ûl¸V0D_åC¥ûT¯ü{öJ¯á¸xp éÕ±³ÀÇ À²nwÙ¿4ëRÓ®É0ÌJüãáOÐ,бX,h³ÿÞÆà?PK »´ÚJðÑøD y" |